From 8d2c8110785fe024ab85480189a80060d295d81f Mon Sep 17 00:00:00 2001 From: Lehi Toskin Date: Thu, 2 Feb 2017 16:05:31 -0800 Subject: [PATCH 01/11] FLIF support WIP - metadata issues --- README.md | 8 + base.rkt | 408 +++++++++++++++++++++++++++++++++++++++----------- doc/ivy.1.bz2 | Bin 2681 -> 2772 bytes doc/ivy.html | 2 + doc/ivy.md | 7 + embed.rkt | 118 +++++++++++++-- files.rkt | 3 + frame.rkt | 47 ++++-- main.rkt | 6 +- 9 files changed, 481 insertions(+), 118 deletions(-) diff --git a/README.md b/README.md index 828f397..0fbc30f 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ tracked solely by the application. - [gif-image](https://github.com/lehitoskin/gif-image) - [png-image](https://github.com/lehitoskin/png-image) - [racquel](https://github.com/brown131/racquel) +- [riff](https://github.com/lehitoskin/riff) (which uses FLIF) - [rsvg](https://github.com/takikawa/rsvg) (which uses librsvg) - [sugar](https://github.com/mbutterick/sugar) - [txexpr](https://github.com/mbutterick/txexpr) @@ -121,6 +122,13 @@ Animated GIF support is currently marked as experimental. To animate a GIF, select View -> GIF Animation. Due to the unstable nature of the related code, know that some GIF's may not load properly or at all. +### FLIF Support + +[FLIF](https://github.com/FLIF-hub/FLIF) support is marked as experimental! To +load a FLIF file in Ivy, simply open like any other image. Ivy will load the +image progressively and a little percentage meter will appear prepended to +the image's filename in the title of the frame. + ### Embedding Tags as XMP metadata Ivy embeds the taglist as XMP metadata in the file itself (if that file supports diff --git a/base.rkt b/base.rkt index 3246a82..442c73e 100644 --- a/base.rkt +++ b/base.rkt @@ -15,6 +15,7 @@ racket/list racket/path racket/string + riff rsvg (only-in srfi/13 string-contains-ci @@ -55,22 +56,27 @@ (define image-bmp (make-bitmap 50 50)) ; directory containing the currently displayed image (define image-dir (make-parameter (find-system-path 'home-dir))) -(define supported-extensions '(".bmp" ".gif" ".jpe" ".jpeg" ".JPEG" ".jpg" ".JPG" ".png" ".svg")) +(define supported-extensions + '(".bmp" ".flif" ".gif" ".jpe" ".jpeg" ".JPEG" ".jpg" ".JPG" ".png" ".svg")) +; gif/flif stuff +; listof pict? +(define image-lst-master empty) +(define image-lst empty) +; listof real? +(define image-lst-timings empty) +; number of times to loop a FLIF (0 = forever) +(define image-num-loops 0) +(define animation-thread (make-parameter #f)) +(define decoder-thread (make-parameter #f)) +; GIF cumulative animation +(define cumulative? (make-parameter #f)) (define exact-search? (make-parameter #f)) + (define color-white (make-object color% "white")) (define color-black (make-object color% "black")) (define color-spring-green (make-object color% "spring green")) (define color-gold (make-object color% "gold")) -; animated gif stuff -; listof pict? -(define master-gif empty) -(define gif-lst empty) -; listof real? -(define gif-lst-timings empty) -(define gif-thread (make-parameter #f)) -(define cumulative? (make-parameter #f)) - ; contract for image scaling (define image-scale/c (or/c 'default @@ -151,6 +157,104 @@ (send parent add-child (car kids)) (add-children parent (cdr kids)))) +; obtain the rgba pixels from the given FLIF image-ptr +(define (flif-get-rgba image-ptr reader width height [y 0] [bstr #""]) + (cond [(= y height) bstr] + [else + (define row (reader image-ptr y (* width 4))) + (flif-get-rgba image-ptr reader width height (+ y 1) (bytes-append bstr row))])) + +;; COPIED FROM opengl/main +;; Convert argb -> rgba +;; Modern wisdom is not to convert to rgba but rather use +;; GL_BGRA with GL_UNSIGNED_INT_8_8_8_8_REV. But that turns out not +;; to work on some implementations, even ones which advertise +;; OpenGL 1.2 support. Great. +(define (argb->rgba! pixels) + (for ([i (in-range (/ (bytes-length pixels) 4))]) + (let* ([offset (* 4 i)] + [alpha (bytes-ref pixels (+ 0 offset))] + [red (bytes-ref pixels (+ 1 offset))] + [green (bytes-ref pixels (+ 2 offset))] + [blue (bytes-ref pixels (+ 3 offset))]) + (bytes-set! pixels (+ 0 offset) red) + (bytes-set! pixels (+ 1 offset) green) + (bytes-set! pixels (+ 2 offset) blue) + (bytes-set! pixels (+ 3 offset) alpha)))) +;; + +(define (rgba->argb! pixels) + (for ([i (in-range (/ (bytes-length pixels) 4))]) + (let* ([offset (* 4 i)] + [red (bytes-ref pixels (+ 0 offset))] + [green (bytes-ref pixels (+ 1 offset))] + [blue (bytes-ref pixels (+ 2 offset))] + [alpha (bytes-ref pixels (+ 3 offset))]) + (bytes-set! pixels (+ 0 offset) alpha) + (bytes-set! pixels (+ 1 offset) red) + (bytes-set! pixels (+ 2 offset) green) + (bytes-set! pixels (+ 3 offset) blue)))) + +(define (flif->list dec-ptr) + ; only look at the first frame if we want a static image + (define num (if (want-animation?) + (flif-decoder-num-images dec-ptr) + 1)) + (for/list ([i (in-range num)]) + (define image (flif-decoder-get-image dec-ptr i)) + (define width (flif-image-get-width image)) + (define height (flif-image-get-height image)) + ; make sure to decode with the proper depth + (define reader (if (= (flif-image-get-depth image) 8) + flif-image-read-row-rgba8 + flif-image-read-row-rgba16)) + (define pixels (flif-get-rgba image reader width height)) + (rgba->argb! pixels) + (define bitmap (make-object bitmap% width height)) + (send bitmap set-argb-pixels 0 0 width height pixels) + bitmap)) + +(define (progressive-callback quality num-read) + (define lst (flif->list (decoder))) + (set! image-bmp-master (first lst)) + (cond [(and (want-animation?) (> (length lst) 1)) + (set! image-lst-timings + (let ([image (flif-decoder-get-image (decoder) 0)]) + (define timing (flif-image-get-frame-delay image)) + (flif-destroy-image! image) + (make-list (length lst) timing))) + (load-image (map bitmap lst) 'default)] + [else (load-image (first lst) 'default)]) + ; set the new frame label + (define-values (base name must-be-dir?) (split-path (image-path))) + (send (send (ivy-canvas) get-parent) + set-label + (format "(~a%) ~a" (exact->inexact (/ quality 100)) (path->string name))) + ; set the gui information + (define size (file-size (image-path))) + (send (status-bar-dimensions) + set-label + (format "~a x ~a pixels ~a" + (send image-bmp-master get-width) + (send image-bmp-master get-height) + (cond [(>= size (expt 2 20)) + (format "~a MiB" + (~r (exact->inexact (/ size (expt 2 20))) + #:precision 1))] + [(>= size (expt 2 10)) + (format "~a KiB" + (~r (exact->inexact (/ size (expt 2 10))) + #:precision 1))] + [else + (format "~a B" size)]))) + (send (status-bar-position) + set-label + (format "~a / ~a" + (+ (get-index (image-path) (pfs)) 1) + (length (pfs)))) + ; the fewer the calls, the faster the total decoding + (+ quality 5000)) + ; objects that will be used extensively in transparency-grid (define dgray-color (make-object color% 128 128 128)) (define lgray-color (make-object color% 204 204 204)) @@ -383,12 +487,12 @@ (define incoming-tags (make-parameter "")) (define want-animation? (make-parameter #f)) -(define/contract (animated-gif-callback canvas dc lst) +(define/contract (animation-callback canvas dc lst) (-> (is-a?/c canvas%) (is-a?/c dc<%>) list? void?) - (define gif-x (inexact->exact (round (pict-width (first lst))))) - (define gif-y (inexact->exact (round (pict-height (first lst))))) - (define gif-center-x (/ gif-x 2)) - (define gif-center-y (/ gif-y 2)) + (define img-x (inexact->exact (round (pict-width (first lst))))) + (define img-y (inexact->exact (round (pict-height (first lst))))) + (define img-center-x (/ img-x 2)) + (define img-center-y (/ img-y 2)) (define canvas-x (send canvas get-width)) (define canvas-y (send canvas get-height)) @@ -400,21 +504,24 @@ ; the left and top offsets for each frame in the gif, ; just in case the frames are of varying sizes (define left/top - (for/list ([bit-frame (gif-images (image-path))] - [master-frame (in-list master-gif)] - [gif-frame (in-list gif-lst)]) - (define master-width (pict-width master-frame)) - (define master-height (pict-height master-frame)) - (define gif-width (pict-width gif-frame)) - (define gif-height (pict-height gif-frame)) - ; grab the frame's image descriptor - ; starting with the graphics control extension - (define matched (car (regexp-match-positions (byte-regexp (bytes #x21 #xf9)) bit-frame))) - (define lengths (subbytes bit-frame (+ (car matched) 9) (+ (car matched) 13))) - (define left (bytes (bytes-ref lengths 1) (bytes-ref lengths 0))) - (define top (bytes (bytes-ref lengths 3) (bytes-ref lengths 2))) - (list (* (string->number (bytes->hex-string left) 16) (/ gif-width master-width)) - (* (string->number (bytes->hex-string top) 16) (/ gif-height master-height))))) + (cond + [(gif? (image-path)) + (for/list ([bit-frame (gif-images (image-path))] + [master-frame (in-list image-lst-master)] + [gif-frame (in-list image-lst)]) + (define master-width (pict-width master-frame)) + (define master-height (pict-height master-frame)) + (define gif-width (pict-width gif-frame)) + (define gif-height (pict-height gif-frame)) + ; grab the frame's image descriptor + ; starting with the graphics control extension + (define matched (car (regexp-match-positions (byte-regexp (bytes #x21 #xf9)) bit-frame))) + (define lengths (subbytes bit-frame (+ (car matched) 9) (+ (car matched) 13))) + (define left (bytes (bytes-ref lengths 1) (bytes-ref lengths 0))) + (define top (bytes (bytes-ref lengths 3) (bytes-ref lengths 2))) + (list (* (string->number (bytes->hex-string left) 16) (/ gif-width master-width)) + (* (string->number (bytes->hex-string top) 16) (/ gif-height master-height))))] + [else (make-list len (list 0 0))])) ; determine x and y placement as well as ; modify the scrollbars outside the animation loop @@ -422,39 +529,82 @@ (define y-loc 0) (cond ; if the image is really big, place it at (0,0) - [(and (> gif-x canvas-x) - (> gif-y canvas-y)) + [(and (> img-x canvas-x) + (> img-y canvas-y)) ; x-loc and y-loc are already 0 (send canvas show-scrollbars #t #t)] ; if the image is wider than the canvas, place it at (0,y) - [(> gif-x canvas-x) + [(> img-x canvas-x) (send canvas show-scrollbars #t #f) ; x-loc is already 0 - (set! y-loc (- canvas-center-y gif-center-y))] + (set! y-loc (- canvas-center-y img-center-y))] ; if the image is taller than the canvas, place it at (x,0) - [(> gif-y canvas-y) + [(> img-y canvas-y) (send canvas show-scrollbars #f #t) ; y-loc is already 0 - (set! x-loc (- canvas-center-x gif-center-x))] + (set! x-loc (- canvas-center-x img-center-x))] ; otherwise, place it at the center of the canvas [else (send canvas show-scrollbars #f #f) - (set! x-loc (- canvas-center-x gif-center-x)) - (set! y-loc (- canvas-center-y gif-center-y))]) + (set! x-loc (- canvas-center-x img-center-x)) + (set! y-loc (- canvas-center-y img-center-y))]) ; actual animation loop - ; runs until gif-thread is killed - (let loop ([gif-frame (first lst)] - [timing (first gif-lst-timings)] + ; runs until animation-thread is killed + (let loop ([img-frame (first lst)] + [timing + (if (empty? image-lst-timings) + 1/20 + (first image-lst-timings))] [offsets (first left/top)] - [i 0]) + [i 0] + [times 0]) ; remove any previous frames from the canvas (unless (cumulative?) (send dc clear)) - (draw-pict gif-frame dc (+ x-loc (first offsets)) (+ y-loc (second offsets))) + (draw-pict img-frame dc (+ x-loc (first offsets)) (+ y-loc (second offsets))) (sleep timing) - (if (>= i len) - (loop (first lst) (first gif-lst-timings) (first left/top) 0) - (loop (list-ref lst i) (list-ref gif-lst-timings i) (list-ref left/top i) (add1 i))))) + (cond + ; loop forever + ; ran through every frame + [(and (>= i len) (= image-num-loops 0)) + (loop (first lst) (first image-lst-timings) (first left/top) 0 0)] + ; loop forever + ; still need to display the other frames + [(and (not (>= i len)) (= image-num-loops 0)) + (loop (list-ref lst i) + ; hacky because decoding a FLIF is very slow right now + (if (empty? image-lst-timings) + 1/20 + (list-ref image-lst-timings i)) + (list-ref left/top i) + (add1 i) + 0)] + ; stop looping eventually + ; ran through every frame, increment times + [(and (>= i len) (not (= image-num-loops 0))) + (loop (first lst) + ; hacky because decoding a FLIF is very slow right now + (if (empty? image-lst-timings) + 1/20 + (first image-lst-timings)) + (first left/top) + 0 + (add1 times))] + ; stop looping eventually + ; still need to display the other frames + [(and (not (>= i len)) + (not (= image-num-loops 0))) + (loop (list-ref lst i) + ; hacky because decoding a FLIF is very slow right now + (if (empty? image-lst-timings) + 1/20 + (list-ref image-lst-timings i)) + (list-ref left/top i) + (add1 i) + times)] + ; reached the end of our allowed loops, do nothing + [(and (>= i len) (not (= image-num-loops 0))) #t] + [else #t]))) ; procedure that loads the given image to the canvas ; takes care of updating the dimensions message and @@ -492,9 +642,9 @@ (path->string name) (exn-message e)) ; set the gifs to defaults - (set! master-gif empty) - (set! gif-lst empty) - (set! gif-lst-timings empty) + (set! image-lst-master empty) + (set! image-lst empty) + (set! image-lst-timings empty) ; just load the static image instead (load-image (bitmap img)) (send sbe set-label @@ -508,9 +658,9 @@ (send bmp load-file bmp-in-port 'gif/alpha) (close-input-port bmp-in-port) (bitmap bmp))) - (set! master-gif lst) - (set! gif-lst (map (λ (gif-frame) (scale-image gif-frame scale)) lst)) - (set! gif-lst-timings (gif-timings img)) + (set! image-lst-master lst) + (set! image-lst (map (λ (gif-frame) (scale-image gif-frame scale)) lst)) + (set! image-lst-timings (gif-timings img)) (set! image-pict #f)) (define size (file-size (image-path))) (send sbd set-label @@ -531,38 +681,115 @@ (format "~a / ~a" (+ (get-index img (pfs)) 1) (length (pfs))))] + ; load animated flif + [(and (want-animation?) (flif? img)) + (cumulative? #f) + (decoder (flif-create-decoder)) + ; progressive decoding + (flif-decoder-set-callback! (decoder) progressive-callback) + (flif-decoder-set-first-callback-quality! (decoder) 1000) + ; put the actual decoding in its own thread + (decoder-thread + (thread (λ () + (flif-decoder-decode-file! (decoder) img) + (define num-frames (flif-decoder-num-images (decoder))) + (define image (flif-decoder-get-image (decoder) 0)) + (set! image-lst-timings + (make-list num-frames + (/ (flif-image-get-frame-delay image) 1000))) + (set! image-num-loops (flif-decoder-num-loops (decoder))) + (displayln "decoder-thread; destroying decoder...") + (flif-destroy-decoder! (decoder)) + (displayln "Done") + (decoder #f)))) + ; set the new frame label + (send (send canvas get-parent) set-label (path->string name)) + ; set the gui information + (define size (file-size (image-path))) + (define dimensions (flif-dimensions (image-path))) + (send sbd set-label + (format "~a x ~a pixels ~a" + (first dimensions) + (second dimensions) + (cond [(>= size (expt 2 20)) + (format "~a MiB" + (~r (exact->inexact (/ size (expt 2 20))) + #:precision 1))] + [(>= size (expt 2 10)) + (format "~a KiB" + (~r (exact->inexact (/ size (expt 2 10))) + #:precision 1))] + [else + (format "~a B" size)]))) + (send sbp set-label + (format "~a / ~a" + (+ (get-index img (pfs)) 1) + (length (pfs)))) + (collect-garbage)] ; else load the static image [else ; make sure the bitmap loaded correctly (define load-success - (if (bytes=? (path-get-extension img) #".svg") - (and (set! image-bmp-master (load-svg-from-file img)) #t) - (send image-bmp-master load-file img 'unknown/alpha))) + (cond [(bytes=? (path-get-extension img) #".svg") + (and (set! image-bmp-master (load-svg-from-file img)) #t)] + [(flif? img) + (cumulative? #f) + (decoder (flif-create-decoder)) + ; progressive decoding + (flif-decoder-set-callback! (decoder) progressive-callback) + (flif-decoder-set-first-callback-quality! (decoder) 1000) + ; put the actual decoding in its own thread + (decoder-thread + (thread (λ () + (flif-decoder-decode-file! (decoder) img) + (flif-destroy-decoder! (decoder)) + (decoder #f)))) + (collect-garbage) + #t] + [else (send image-bmp-master load-file img 'unknown/alpha)])) (cond [load-success (send (send canvas get-parent) set-label (path->string name)) (set! image-pict (scale-image image-bmp-master scale)) (set! image-bmp (pict->bitmap (transparency-grid-append image-pict))) (define size (file-size (image-path))) - (send sbd set-label - (format "~a x ~a pixels ~a" - (send image-bmp-master get-width) - (send image-bmp-master get-height) - (cond [(>= size (expt 2 20)) - (format "~a MiB" - (~r (exact->inexact (/ size (expt 2 20))) - #:precision 1))] - [(>= size (expt 2 10)) - (format "~a KiB" - (~r (exact->inexact (/ size (expt 2 10))) - #:precision 1))] - [else - (format "~a B" size)]))) + (cond + [(flif? (image-path)) + (define dimensions (flif-dimensions (image-path))) + (send sbd set-label + (format "~a x ~a pixels ~a" + (first dimensions) + (second dimensions) + (cond [(>= size (expt 2 20)) + (format "~a MiB" + (~r (exact->inexact (/ size (expt 2 20))) + #:precision 1))] + [(>= size (expt 2 10)) + (format "~a KiB" + (~r (exact->inexact (/ size (expt 2 10))) + #:precision 1))] + [else + (format "~a B" size)])))] + [else + (send sbd set-label + (format "~a x ~a pixels ~a" + (send image-bmp-master get-width) + (send image-bmp-master get-height) + (cond [(>= size (expt 2 20)) + (format "~a MiB" + (~r (exact->inexact (/ size (expt 2 20))) + #:precision 1))] + [(>= size (expt 2 10)) + (format "~a KiB" + (~r (exact->inexact (/ size (expt 2 10))) + #:precision 1))] + [else + (format "~a B" size)])))]) (send sbp set-label (format "~a / ~a" (+ (get-index img (pfs)) 1) (length (pfs)))) - (set! gif-lst empty) - (set! gif-lst-timings empty)] + (set! image-lst empty) + (set! image-lst-timings empty)] [else (eprintf "Error loading file ~v\n" img) (send sbe set-label @@ -601,31 +828,31 @@ (send tag-tfield refresh)] [(list? img) ; scale the image in the desired direction - (set! gif-lst (map (λ (pct) (scale-image pct scale)) img))] + (set! image-lst (map (λ (pct) (scale-image pct scale)) img))] [else ; we already have the image loaded - (set! master-gif empty) - (set! gif-lst empty) - (set! gif-lst-timings empty) + (set! image-lst-master empty) + (set! image-lst empty) + (set! image-lst-timings empty) (set! image-pict (scale-image img scale)) (set! image-bmp (pict->bitmap (transparency-grid-append image-pict)))]) - (unless (or (false? (gif-thread)) (thread-dead? (gif-thread))) - (kill-thread (gif-thread))) + (unless (or (false? (animation-thread)) (thread-dead? (animation-thread))) + (kill-thread (animation-thread))) - (if (not (empty? gif-lst)) + (if (not (empty? image-lst)) ; gif-lst contains a list of picts, display the animated gif (send canvas set-on-paint! (λ (canvas dc) - (unless (or (false? (gif-thread)) (thread-dead? (gif-thread))) - (kill-thread (gif-thread))) + (unless (or (false? (animation-thread)) (thread-dead? (animation-thread))) + (kill-thread (animation-thread))) (send dc set-background "black") - (gif-thread + (animation-thread (thread (λ () - (animated-gif-callback canvas dc gif-lst)))))) + (animation-callback canvas dc image-lst)))))) ; otherwise, display the static image (send canvas set-on-paint! (λ (canvas dc) @@ -674,8 +901,8 @@ (- canvas-center-y img-center-y))])))) ; tell the scrollbars to adjust for the size of the image - (let ([img-x (inexact->exact (round (pict-width (if image-pict image-pict (first gif-lst)))))] - [img-y (inexact->exact (round (pict-height (if image-pict image-pict (first gif-lst)))))]) + (let ([img-x (inexact->exact (round (pict-width (if image-pict image-pict (first image-lst)))))] + [img-y (inexact->exact (round (pict-height (if image-pict image-pict (first image-lst)))))]) ; will complain if width/height is less than 1 (define width (if (< img-x 1) 1 img-x)) (define height (if (< img-y 1) 1 img-y)) @@ -720,9 +947,16 @@ ; mmm... curry (define ((load-image-in-collection direction)) (unless (equal? (image-path) root-path) - ; kill the gif thread, if applicable - (unless (or (false? (gif-thread)) (thread-dead? (gif-thread))) - (kill-thread (gif-thread))) + ; kill the animation thread, if applicable + (unless (or (false? (animation-thread)) (thread-dead? (animation-thread))) + (kill-thread (animation-thread))) + (unless (or (false? (decoder-thread)) (thread-dead? (decoder-thread))) + (kill-thread (decoder-thread)) + (displayln "load-image-in-collection; aborting decoder...") + (flif-abort-decoder! (decoder)) + (flif-destroy-decoder! (decoder)) + (displayln "done") + (decoder #f)) (send (ivy-tag-tfield) set-field-background color-white) (define prev-index (get-index (image-path) (pfs))) (case direction diff --git a/doc/ivy.1.bz2 b/doc/ivy.1.bz2 index d911f9acf78cadea8e7b6233a4d9963e76d01614..114adf58c667487e441b9756098813a50a3eea47 100644 GIT binary patch literal 2772 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 Browser

From the tag browser it is also possible to delete entire tag categories or rename them. Also, it is possible to edit the taglist of an image by clicking on an image path and selecting "Edit Tags" from the menu.

Animated GIF Support

Animated GIF support is currently marked as experimental. To animate a GIF, select View -> GIF Animation. Due to the unstable nature of the related code, know that some GIF's may not load properly or at all.

+

FLIF Support

+

FLIF support is marked as experimental! To load a FLIF file in Ivy, simply open like any other image. Ivy will load the image progressively and a little percentage meter will appear prepended to the image's filename in the title of the frame.

Embedding Tags as XMP metadata

Ivy embeds the taglist as XMP metadata in the file itself (if that file supports XMP metadata). That way if you move your images around, the tags will stay the same. However, the information in the database will then be out of date, so it is recommended that if you move files around, utilize Ivy's command-line interface with the switch -M to ensure the changes are tracked.

OPTIONS

diff --git a/doc/ivy.md b/doc/ivy.md index 11b82d1..8c1c58f 100644 --- a/doc/ivy.md +++ b/doc/ivy.md @@ -84,6 +84,13 @@ Animated GIF support is currently marked as experimental. To animate a GIF, select View -> GIF Animation. Due to the unstable nature of the related code, know that some GIF's may not load properly or at all. +## FLIF Support + +FLIF support is marked as experimental! To load a FLIF file in Ivy, simply open +like any other image. Ivy will load the image progressively and a little +percentage meter will appear prepended to the image's filename in the title of +the frame. + ## Embedding Tags as XMP metadata Ivy embeds the taglist as XMP metadata in the file itself (if that file supports diff --git a/embed.rkt b/embed.rkt index d22657a..f6e33a0 100644 --- a/embed.rkt +++ b/embed.rkt @@ -12,9 +12,10 @@ racket/format racket/list racket/port + riff txexpr xml - (only-in "files.rkt" ivy-version)) + (only-in "files.rkt" ivy-version decoder)) (provide add-embed-tags! dc:subject->list del-embed-tags! @@ -168,19 +169,61 @@ GIF XMP keyword: #"XMP Data" with auth #"XMP" (define (svg-has-tag? in bstr) (and (regexp-try-match (byte-regexp bstr) in) #t)) +#| FLIF stuff |# + + + #| Embedding stuff |# (define/contract (embed-support? img) (any/c . -> . boolean?) - (or (gif? img) (jpeg? img) (png? img) (svg? img))) + (or (flif? img) (gif? img) (jpeg? img) (png? img) (svg? img))) (define/contract (add-embed-tags! img taglist) (embed-support? list? . -> . void?) - (cond [(gif? img) (add-embed-gif! img taglist)] + (cond [(flif? img) (add-embed-flif! img taglist)] + [(gif? img) (add-embed-gif! img taglist)] [(jpeg? img) (add-embed-jpeg! img taglist)] [(png? img) (add-embed-png! img taglist)] [(svg? img) (add-embed-svg! img taglist)])) +; adds the taglist to the existing tags +; if there are no existing tags, set them +(define (add-embed-flif! flif taglist) + (eprintf "add-embed-flif! flif: ~v, taglist: ~v\n") + ; get the image pointer + (define image (flif-decoder-get-image (decoder) 0)) + (define old-xmp (flif-image-get-metadata image "eXmp")) + (define old-tags (get-embed-tags flif)) + (define reconciled (remove-duplicates (append old-tags taglist))) + (define xexpr (if (bytes=? old-xmp #"") + ; if the image has no xmp data, generate some + (make-xmp-xexpr taglist) + (set-dc:subject (string->xexpr (bytes->string/utf-8 old-xmp)) reconciled))) + (define bstr (string->bytes/utf-8 (xexpr->xmp xexpr))) + ; set the xmp inside the image pointer + (flif-image-set-metadata! image "eXmp" bstr) + ; re-encode and save the new flif + (eprintf "Encoding memory...") + (define encoder (flif-create-encoder)) + (define encoded + (cond [(= (flif-decoder-num-images (decoder)) 1) + (eprintf "Only adding one image\n") + (flif-encoder-add-image! encoder image) + (flif-encoder-encode-memory encoder)] + [else + (eprintf "Adding multiple images\n") + (for ([i (in-range (flif-decoder-num-images (decoder)))]) + (define img (flif-decoder-get-image (decoder i))) + (flif-encoder-add-image! encoder img)) + (flif-encoder-encode-memory encoder)])) + (flif-destroy-encoder! encoder) + (eprintf "Done\nSaving file.\n") + (with-output-to-file flif + (λ () (display encoded)) + #:mode 'binary + #:exists 'truncate/replace)) + ; adds taglist to the existing tags ; if there are no existing tags, set them (define (add-embed-png! png taglist) @@ -199,8 +242,7 @@ GIF XMP keyword: #"XMP Data" with auth #"XMP" (define new-hash (itxt-set png-hash itxt-hash "XML:com.adobe.xmp")) (define new-png (hash->png new-hash)) (with-output-to-file png - (λ () - (display new-png)) + (λ () (display new-png)) #:mode 'binary #:exists 'truncate/replace)) @@ -226,18 +268,61 @@ GIF XMP keyword: #"XMP Data" with auth #"XMP" (define/contract (set-embed-tags! img taglist) (embed-support? list? . -> . void?) - (cond [(gif? img) (set-embed-gif! img taglist)] + (cond [(flif? img) (set-embed-flif! img taglist)] + [(gif? img) (set-embed-gif! img taglist)] [(jpeg? img) (set-embed-jpeg! img taglist)] [(png? img) (set-embed-png! img taglist)] [(svg? img) (set-embed-svg! img taglist)])) (define/contract (set-embed-xmp! img xmp-str) (embed-support? string? . -> . void?) - (cond [(gif? img) (set-xmp-gif! img xmp-str)] + (cond [(flif? img) (set-xmp-flif! img xmp-str)] + [(gif? img) (set-xmp-gif! img xmp-str)] [(jpeg? img) (set-xmp-jpeg! img xmp-str)] [(png? img) (set-xmp-png! img xmp-str)] [(svg? img) (set-xmp-svg! img xmp-str)])) +(define (set-xmp-flif! flif xmp-str) + (eprintf "set-xmp-flif! flif: ~v, xmp-str: ~v\n" flif xmp-str) + (define bstr (string->bytes/utf-8 xmp-str)) + (define image (flif-decoder-get-image (decoder) 0)) + (flif-image-set-metadata! image "eXmp" bstr) + ; re-encode and save the new flif + (eprintf "Encoding data...\n") + (define encoder (flif-create-encoder)) + (define encoded + (cond [(= (flif-decoder-num-images (decoder)) 1) + (eprintf "Adding only one image\n") + (flif-encoder-add-image! encoder image) + (flif-encoder-encode-memory encoder)] + [else + (eprintf "Adding multiple images\n") + (for ([i (in-range (flif-decoder-num-images (decoder)))]) + (define img (flif-decoder-get-image (decoder i))) + (flif-encoder-add-image! encoder img)) + (flif-encoder-encode-memory encoder)])) + (eprintf "Done\nSaving new data to file\n") + (with-output-to-file flif + (λ () (display encoded)) + #:mode 'binary + #:exists 'truncate/replace)) + +(define (set-embed-flif! flif taglist) + ; get the image pointer + (eprintf "set-embed-flif! flif: ~v, taglist: ~v\n" flif taglist) + (eprintf "Getting image from decoder... ") + (define image (flif-decoder-get-image (decoder) 0)) + (eprintf "Done!\n") + (eprintf "Getting old metadata... ") + (define old-xmp (flif-image-get-metadata image "eXmp")) + (eprintf "Done!\n") + (define xexpr (if (bytes=? old-xmp #"") + ; if the image has no xmp data, generate some + (make-xmp-xexpr taglist) + (set-dc:subject (string->xexpr (bytes->string/utf-8 old-xmp)) taglist))) + (define xmp-str (xexpr->xmp xexpr)) + (set-xmp-flif! flif xmp-str)) + ; set the XMP data inside the image's iTXt chunk (define (set-xmp-png! png xmp-str) (define png-hash (png->hash png)) @@ -246,8 +331,7 @@ GIF XMP keyword: #"XMP Data" with auth #"XMP" (define new-hash (itxt-set png-hash itxt-hash "XML:com.adobe.xmp")) (define new-png (hash->png new-hash)) (with-output-to-file png - (λ () - (display new-png)) + (λ () (display new-png)) #:mode 'binary #:exists 'truncate/replace)) @@ -362,8 +446,7 @@ GIF XMP keyword: #"XMP Data" with auth #"XMP" (bytes #x3b) (subbytes bstr (+ (first (first pos-pair)) (second (first pos-pair)))))) (with-output-to-file gif - (λ () - (printf "~a~a~a" before-bstr new-appn-xmp after-bstr)) + (λ () (printf "~a~a~a" before-bstr new-appn-xmp after-bstr)) #:mode 'binary #:exists 'truncate/replace)) @@ -412,8 +495,7 @@ GIF XMP keyword: #"XMP Data" with auth #"XMP" #"" after)) (with-output-to-file svg - (λ () - (display xmp-bstr)) + (λ () (display xmp-bstr)) #:mode 'binary #:exists 'truncate/replace)) @@ -446,11 +528,19 @@ GIF XMP keyword: #"XMP Data" with auth #"XMP" (define/contract (get-embed-xmp img) (embed-support? . -> . list?) - (cond [(gif? img) (get-embed-gif img)] + (cond [(flif? img) (get-embed-flif img)] + [(gif? img) (get-embed-gif img)] [(jpeg? img) (get-embed-jpeg img)] [(png? img) (get-embed-png img)] [(svg? img) (get-embed-svg img)])) +(define (get-embed-flif flif) + (define image (flif-decoder-get-image (decoder) 0)) + (define xmp (flif-image-get-metadata image "eXmp")) + (if (bytes=? xmp #"") + empty + (list xmp))) + ; retrieve the XMP data located inside the iTXt block(s) (define (get-embed-png png) (define png-hash (if (hash? png) png (png->hash png))) diff --git a/files.rkt b/files.rkt index 33e4586..c6af5ac 100644 --- a/files.rkt +++ b/files.rkt @@ -48,3 +48,6 @@ (unless (directory-exists? thumbnails-path) (make-directory thumbnails-path)) + +; flif decoder parameter +(define decoder (make-parameter #f)) diff --git a/frame.rkt b/frame.rkt index eb7d604..589752b 100644 --- a/frame.rkt +++ b/frame.rkt @@ -11,6 +11,7 @@ racket/math racket/path racket/string + riff txexpr xml "base.rkt" @@ -31,8 +32,8 @@ (exit:insert-on-callback (λ () ; kill the gif thread, if applicable - (unless (or (false? (gif-thread)) (thread-dead? (gif-thread))) - (kill-thread (gif-thread))) + (unless (or (false? (animation-thread)) (thread-dead? (animation-thread))) + (kill-thread (animation-thread))) ; wait for any xmp threads to finish before exiting (unless (hash-empty? xmp-threads) (for ([pair (in-list (hash->list xmp-threads))]) @@ -41,6 +42,13 @@ (printf "Waiting for thread ~a to finish...\n" (car pair)) (sleep 1/4) (loop))))) + (unless (or (false? (decoder-thread)) (thread-dead? (decoder-thread))) + (kill-thread (decoder-thread)) + (displayln "Quit; aborting decoder...") + (flif-abort-decoder! (decoder)) + (flif-destroy-decoder! (decoder)) + (displayln "done") + (decoder #f)) (disconnect sqlc)))) (define closer-frame% @@ -290,8 +298,15 @@ [help-string "Empties the current collection"] [callback (λ (i e) - (unless (or (false? (gif-thread)) (thread-dead? (gif-thread))) - (kill-thread (gif-thread))) + (unless (or (false? (animation-thread)) (thread-dead? (animation-thread))) + (kill-thread (animation-thread))) + (unless (or (false? (decoder-thread)) (thread-dead? (decoder-thread))) + (kill-thread (decoder-thread)) + (displayln "New collection; aborting decoder...") + (flif-abort-decoder! (decoder)) + (flif-destroy-decoder! (decoder)) + (displayln "done") + (decoder #f)) (image-dir (find-system-path 'home-dir)) (pfs (list root-path)) (image-path root-path) @@ -467,9 +482,9 @@ [callback (λ (i e) (unless (equal? (image-path) root-path) (collect-garbage 'incremental) - (if (empty? master-gif) + (if (empty? image-lst-master) (load-image (bitmap image-bmp-master) n) - (load-image master-gif n))))])) + (load-image image-lst-master n))))])) (define ivy-menu-bar-view-rotate-left (new menu-item% @@ -671,9 +686,9 @@ along with this program. If not, see ." (unless (equal? (image-path) root-path) (collect-garbage 'incremental) (if (and image-pict - (empty? gif-lst)) + (empty? image-lst)) (load-image image-pict 'larger) - (load-image gif-lst 'larger))))])) + (load-image image-lst 'larger))))])) (define ivy-actions-zoom-out (new button% @@ -684,9 +699,9 @@ along with this program. If not, see ." (unless (equal? (image-path) root-path) (collect-garbage 'incremental) (if (and image-pict - (empty? gif-lst)) + (empty? image-lst)) (load-image image-pict 'smaller) - (load-image gif-lst 'smaller))))])) + (load-image image-lst 'smaller))))])) (define ivy-actions-zoom-normal (new button% @@ -696,7 +711,7 @@ along with this program. If not, see ." ; do nothing if we've pressed ctrl+n (unless (equal? (image-path) root-path) (collect-garbage 'incremental) - (if (empty? gif-lst) + (if (empty? image-lst) (load-image image-bmp-master 'none) (load-image (image-path) 'none))))])) @@ -708,7 +723,7 @@ along with this program. If not, see ." ; do nothing if we've pressed ctrl+n (unless (equal? (image-path) root-path) (collect-garbage 'incremental) - (if (empty? gif-lst) + (if (empty? image-lst) (load-image image-bmp-master) (load-image (image-path)))))])) @@ -951,17 +966,17 @@ along with this program. If not, see ." (unless (equal? (image-path) root-path) (collect-garbage 'incremental) (if (and image-pict - (empty? gif-lst)) + (empty? image-lst)) (load-image image-pict 'wheel-smaller) - (load-image gif-lst 'wheel-smaller)))] + (load-image image-lst 'wheel-smaller)))] [(wheel-up) ; do nothing if we've pressed ctrl+n (unless (equal? (image-path) root-path) (collect-garbage 'incremental) (if (and image-pict - (empty? gif-lst)) + (empty? image-lst)) (load-image image-pict 'wheel-larger) - (load-image gif-lst 'wheel-larger)))] + (load-image image-lst 'wheel-larger)))] ; osx does things a little different [(f11) (unless (macosx?) (toggle-fullscreen this ivy-frame))] diff --git a/main.rkt b/main.rkt index c4a2d63..498966b 100755 --- a/main.rkt +++ b/main.rkt @@ -9,6 +9,7 @@ racket/list racket/path racket/string + riff txexpr xml "base.rkt" @@ -175,7 +176,10 @@ (define max-width (- monitor-width canvas-offset 100)) (define max-height (- monitor-height 100)) (load-image (image-path)) - (define pct (bitmap image-bmp-master)) + (define pct (if (flif? (image-path)) + (let ([dimensions (flif-dimensions (image-path))]) + (rectangle (first dimensions) (second dimensions))) + (bitmap image-bmp-master))) (define pct-width (pict-width pct)) (define pct-height (pict-height pct)) (cond From bfb712df6bfcee048795a98193390dc691ef8524 Mon Sep 17 00:00:00 2001 From: Lehi Toskin Date: Mon, 6 Feb 2017 15:22:58 -0800 Subject: [PATCH 02/11] More work on FLIFs - Get rid of the decoding thread, since if we want metadata, weird things happen if it's not decoded and we try to search for it - Maybe forego the built-in metadata functions and just scan the file instead so I don't have to bother with re-encoding the image data when the XMP is set --- base.rkt | 80 ++++++++++++++++++-------------------- embed.rkt | 113 +++++++++++++++++++++++++++++++++++------------------- frame.rkt | 15 ++++++-- 3 files changed, 123 insertions(+), 85 deletions(-) diff --git a/base.rkt b/base.rkt index 442c73e..66ec111 100644 --- a/base.rkt +++ b/base.rkt @@ -68,6 +68,8 @@ (define image-num-loops 0) (define animation-thread (make-parameter #f)) (define decoder-thread (make-parameter #f)) +; number from the decoder thread that shows how much the image is loaded +(define flif-load-progress (box 0)) ; GIF cumulative animation (define cumulative? (make-parameter #f)) (define exact-search? (make-parameter #f)) @@ -220,9 +222,7 @@ (cond [(and (want-animation?) (> (length lst) 1)) (set! image-lst-timings (let ([image (flif-decoder-get-image (decoder) 0)]) - (define timing (flif-image-get-frame-delay image)) - (flif-destroy-image! image) - (make-list (length lst) timing))) + (make-list (length lst) (flif-image-get-frame-delay image)))) (load-image (map bitmap lst) 'default)] [else (load-image (first lst) 'default)]) ; set the new frame label @@ -230,28 +230,8 @@ (send (send (ivy-canvas) get-parent) set-label (format "(~a%) ~a" (exact->inexact (/ quality 100)) (path->string name))) - ; set the gui information - (define size (file-size (image-path))) - (send (status-bar-dimensions) - set-label - (format "~a x ~a pixels ~a" - (send image-bmp-master get-width) - (send image-bmp-master get-height) - (cond [(>= size (expt 2 20)) - (format "~a MiB" - (~r (exact->inexact (/ size (expt 2 20))) - #:precision 1))] - [(>= size (expt 2 10)) - (format "~a KiB" - (~r (exact->inexact (/ size (expt 2 10))) - #:precision 1))] - [else - (format "~a B" size)]))) - (send (status-bar-position) - set-label - (format "~a / ~a" - (+ (get-index (image-path) (pfs)) 1) - (length (pfs)))) + ; set the load progress + ;(set-box! flif-load-progress quality) ; the fewer the calls, the faster the total decoding (+ quality 5000)) @@ -567,7 +547,10 @@ ; loop forever ; ran through every frame [(and (>= i len) (= image-num-loops 0)) - (loop (first lst) (first image-lst-timings) (first left/top) 0 0)] + (loop (first lst) (if (empty? image-lst-timings) + 1/20 + (first image-lst-timings)) + (first left/top) 0 0)] ; loop forever ; still need to display the other frames [(and (not (>= i len)) (= image-num-loops 0)) @@ -687,21 +670,28 @@ (decoder (flif-create-decoder)) ; progressive decoding (flif-decoder-set-callback! (decoder) progressive-callback) - (flif-decoder-set-first-callback-quality! (decoder) 1000) + (flif-decoder-set-first-callback-quality! (decoder) 100000) ; put the actual decoding in its own thread - (decoder-thread + #;(decoder-thread (thread (λ () + ; set the progress to 0 + (set-box! flif-load-progress 0) + ; decode, but do not immediately destroy the decoder (flif-decoder-decode-file! (decoder) img) (define num-frames (flif-decoder-num-images (decoder))) (define image (flif-decoder-get-image (decoder) 0)) (set! image-lst-timings (make-list num-frames (/ (flif-image-get-frame-delay image) 1000))) - (set! image-num-loops (flif-decoder-num-loops (decoder))) - (displayln "decoder-thread; destroying decoder...") - (flif-destroy-decoder! (decoder)) - (displayln "Done") - (decoder #f)))) + (set! image-num-loops (flif-decoder-num-loops (decoder)))))) + ; regular decoding + (flif-decoder-decode-file! (decoder) img) + (let ([image (flif-decoder-get-image (decoder) 0)] + [num-frames (flif-decoder-num-images (decoder))]) + (set! image-lst-timings + (make-list num-frames + (/ (flif-image-get-frame-delay image) 1000))) + (set! image-num-loops (flif-decoder-num-loops (decoder)))) ; set the new frame label (send (send canvas get-parent) set-label (path->string name)) ; set the gui information @@ -724,8 +714,7 @@ (send sbp set-label (format "~a / ~a" (+ (get-index img (pfs)) 1) - (length (pfs)))) - (collect-garbage)] + (length (pfs))))] ; else load the static image [else ; make sure the bitmap loaded correctly @@ -735,17 +724,18 @@ [(flif? img) (cumulative? #f) (decoder (flif-create-decoder)) + ; set the load progress to 0 + ;(set-box! flif-load-progress 0) ; progressive decoding (flif-decoder-set-callback! (decoder) progressive-callback) - (flif-decoder-set-first-callback-quality! (decoder) 1000) + (flif-decoder-set-first-callback-quality! (decoder) 100000) ; put the actual decoding in its own thread - (decoder-thread + #;(decoder-thread (thread (λ () - (flif-decoder-decode-file! (decoder) img) - (flif-destroy-decoder! (decoder)) - (decoder #f)))) - (collect-garbage) - #t] + ; decode, but do not immediately destroy the decoder + (flif-decoder-decode-file! (decoder) img)))) + ; regular decoding + (flif-decoder-decode-file! (decoder) img)] [else (send image-bmp-master load-file img 'unknown/alpha)])) (cond [load-success (send (send canvas get-parent) set-label (path->string name)) @@ -950,13 +940,17 @@ ; kill the animation thread, if applicable (unless (or (false? (animation-thread)) (thread-dead? (animation-thread))) (kill-thread (animation-thread))) - (unless (or (false? (decoder-thread)) (thread-dead? (decoder-thread))) + #;(unless (or (false? (decoder-thread)) (thread-dead? (decoder-thread))) (kill-thread (decoder-thread)) (displayln "load-image-in-collection; aborting decoder...") (flif-abort-decoder! (decoder)) (flif-destroy-decoder! (decoder)) (displayln "done") (decoder #f)) + (when (decoder) + (flif-abort-decoder! (decoder)) + (flif-destroy-decoder! (decoder)) + (decoder #f)) (send (ivy-tag-tfield) set-field-background color-white) (define prev-index (get-index (image-path) (pfs))) (case direction diff --git a/embed.rkt b/embed.rkt index f6e33a0..e0af138 100644 --- a/embed.rkt +++ b/embed.rkt @@ -55,6 +55,8 @@ GIF XMP keyword: #"XMP Data" with auth #"XMP" (define gif-XMP-auth #"XMP") (define gif-XMP-header (bytes-append gif-XMP-id gif-XMP-auth)) +#| JPEG stuff |# + (define (jpeg-xmp? bstr) (and (>= (bytes-length bstr) (+ (bytes-length jpeg-XMP-id) 4)) (bytes=? (subbytes bstr 4 (+ (bytes-length jpeg-XMP-id) 4)) jpeg-XMP-id))) @@ -63,8 +65,6 @@ GIF XMP keyword: #"XMP Data" with auth #"XMP" (and (regexp-try-match (byte-regexp (bytes #xff marker-byte)) in) #t)) -#| JPEG stuff |# - ; path-string, bytes, or input port (define/contract (jpeg? img) (any/c . -> . boolean?) @@ -171,7 +171,11 @@ GIF XMP keyword: #"XMP Data" with auth #"XMP" #| FLIF stuff |# +(define (flif-has-marker? in marker-bytes) + (and (regexp-try-match (byte-regexp marker-bytes) in) #t)) +(define (flif-goto-marker in marker-bytes) + (regexp-match-peek-positions (byte-regexp marker-bytes) in)) #| Embedding stuff |# @@ -190,7 +194,6 @@ GIF XMP keyword: #"XMP Data" with auth #"XMP" ; adds the taglist to the existing tags ; if there are no existing tags, set them (define (add-embed-flif! flif taglist) - (eprintf "add-embed-flif! flif: ~v, taglist: ~v\n") ; get the image pointer (define image (flif-decoder-get-image (decoder) 0)) (define old-xmp (flif-image-get-metadata image "eXmp")) @@ -204,21 +207,17 @@ GIF XMP keyword: #"XMP Data" with auth #"XMP" ; set the xmp inside the image pointer (flif-image-set-metadata! image "eXmp" bstr) ; re-encode and save the new flif - (eprintf "Encoding memory...") (define encoder (flif-create-encoder)) (define encoded (cond [(= (flif-decoder-num-images (decoder)) 1) - (eprintf "Only adding one image\n") (flif-encoder-add-image! encoder image) (flif-encoder-encode-memory encoder)] [else - (eprintf "Adding multiple images\n") (for ([i (in-range (flif-decoder-num-images (decoder)))]) (define img (flif-decoder-get-image (decoder i))) (flif-encoder-add-image! encoder img)) (flif-encoder-encode-memory encoder)])) (flif-destroy-encoder! encoder) - (eprintf "Done\nSaving file.\n") (with-output-to-file flif (λ () (display encoded)) #:mode 'binary @@ -282,40 +281,63 @@ GIF XMP keyword: #"XMP Data" with auth #"XMP" [(png? img) (set-xmp-png! img xmp-str)] [(svg? img) (set-xmp-svg! img xmp-str)])) +; +; TODO: +; If we're encoding, make sure to grab every option possible +; and set that in the encoder we've created. Alternatively, +; do like the other formats and forego the built-in metadata +; function so I don't have to painstakingly re-encode the +; image data every time I set the XMP. +; (define (set-xmp-flif! flif xmp-str) - (eprintf "set-xmp-flif! flif: ~v, xmp-str: ~v\n" flif xmp-str) (define bstr (string->bytes/utf-8 xmp-str)) - (define image (flif-decoder-get-image (decoder) 0)) - (flif-image-set-metadata! image "eXmp" bstr) - ; re-encode and save the new flif - (eprintf "Encoding data...\n") - (define encoder (flif-create-encoder)) - (define encoded - (cond [(= (flif-decoder-num-images (decoder)) 1) - (eprintf "Adding only one image\n") - (flif-encoder-add-image! encoder image) - (flif-encoder-encode-memory encoder)] - [else - (eprintf "Adding multiple images\n") - (for ([i (in-range (flif-decoder-num-images (decoder)))]) - (define img (flif-decoder-get-image (decoder i))) - (flif-encoder-add-image! encoder img)) - (flif-encoder-encode-memory encoder)])) - (eprintf "Done\nSaving new data to file\n") - (with-output-to-file flif - (λ () (display encoded)) - #:mode 'binary - #:exists 'truncate/replace)) + ; in case we're running from the command-line + (cond + [(decoder) + (define image (flif-decoder-get-image (decoder) 0)) + (flif-image-set-metadata! image "eXmp" bstr) + ; re-encode and save the new flif + (define encoder (flif-create-encoder)) + (define encoded + (cond [(= (flif-decoder-num-images (decoder)) 1) + (flif-encoder-add-image! encoder image) + (flif-encoder-encode-memory encoder)] + [else + (for ([i (in-range (flif-decoder-num-images (decoder)))]) + (define img (flif-decoder-get-image (decoder) i)) + (flif-encoder-add-image! encoder img)) + (flif-encoder-encode-memory encoder)])) + (flif-destroy-encoder! encoder) + (with-output-to-file flif + (λ () (display encoded)) + #:mode 'binary + #:exists 'truncate/replace)] + [else + (define decoder (flif-create-decoder)) + (flif-decoder-decode-file! decoder flif) + (define image (flif-decoder-get-image decoder 0)) + ; re-encode and save the new flif + (define encoder (flif-create-encoder)) + (define encoded + (cond [(= (flif-decoder-num-images (decoder)) 1) + (flif-encoder-add-image! encoder image) + (flif-encoder-encode-memory encoder)] + [else + (for ([i (in-range (flif-decoder-num-images decoder))]) + (define img (flif-decoder-get-image (decoder i))) + (flif-encoder-add-image! encoder img)) + (flif-encoder-encode-memory encoder)])) + (flif-destroy-decoder! decoder) + (flif-destroy-encoder! encoder) + (with-output-to-file flif + (λ () (display encoded)) + #:mode 'binary + #:exists 'truncate/replace)])) (define (set-embed-flif! flif taglist) ; get the image pointer - (eprintf "set-embed-flif! flif: ~v, taglist: ~v\n" flif taglist) - (eprintf "Getting image from decoder... ") (define image (flif-decoder-get-image (decoder) 0)) - (eprintf "Done!\n") - (eprintf "Getting old metadata... ") (define old-xmp (flif-image-get-metadata image "eXmp")) - (eprintf "Done!\n") (define xexpr (if (bytes=? old-xmp #"") ; if the image has no xmp data, generate some (make-xmp-xexpr taglist) @@ -535,11 +557,24 @@ GIF XMP keyword: #"XMP Data" with auth #"XMP" [(svg? img) (get-embed-svg img)])) (define (get-embed-flif flif) - (define image (flif-decoder-get-image (decoder) 0)) - (define xmp (flif-image-get-metadata image "eXmp")) - (if (bytes=? xmp #"") - empty - (list xmp))) + ; if we're calling from the command-line, we won't have + ; a proper decoder in place, so create a new one + (cond + [(decoder) + (define image (flif-decoder-get-image (decoder) 0)) + (define xmp (flif-image-get-metadata image "eXmp")) + (if (bytes=? xmp #"") + empty + (list (bytes->string/utf-8 xmp)))] + [else + (define dec (flif-create-decoder)) + (flif-decoder-decode-file! dec flif) + (define image (flif-decoder-get-image (decoder) 0)) + (define xmp (flif-image-get-metadata image "eXmp")) + (flif-destroy-decoder! dec) + (if (bytes=? xmp #"") + empty + (list (bytes->string/utf-8 xmp)))])) ; retrieve the XMP data located inside the iTXt block(s) (define (get-embed-png png) diff --git a/frame.rkt b/frame.rkt index 589752b..d3577d6 100644 --- a/frame.rkt +++ b/frame.rkt @@ -42,13 +42,17 @@ (printf "Waiting for thread ~a to finish...\n" (car pair)) (sleep 1/4) (loop))))) - (unless (or (false? (decoder-thread)) (thread-dead? (decoder-thread))) + #;(unless (or (false? (decoder-thread)) (thread-dead? (decoder-thread))) (kill-thread (decoder-thread)) (displayln "Quit; aborting decoder...") (flif-abort-decoder! (decoder)) (flif-destroy-decoder! (decoder)) (displayln "done") (decoder #f)) + (when (decoder) + (flif-abort-decoder! (decoder)) + (flif-destroy-decoder! (decoder)) + (decoder #f)) (disconnect sqlc)))) (define closer-frame% @@ -300,13 +304,18 @@ (λ (i e) (unless (or (false? (animation-thread)) (thread-dead? (animation-thread))) (kill-thread (animation-thread))) - (unless (or (false? (decoder-thread)) (thread-dead? (decoder-thread))) + #;(unless (or (false? (decoder-thread)) (thread-dead? (decoder-thread))) (kill-thread (decoder-thread)) (displayln "New collection; aborting decoder...") (flif-abort-decoder! (decoder)) + (display "Destroying decoder... ") (flif-destroy-decoder! (decoder)) (displayln "done") (decoder #f)) + (when (decoder) + (flif-abort-decoder! (decoder)) + (flif-destroy-decoder! (decoder)) + (decoder #f)) (image-dir (find-system-path 'home-dir)) (pfs (list root-path)) (image-path root-path) @@ -441,7 +450,7 @@ (define ivy-menu-bar-view-gif-animation (new checkable-menu-item% [parent ivy-menu-bar-view] - [label "&GIF Animation"] + [label "&Animation"] [help-string "Animate GIFs, if possible."] [callback (λ (i e) (want-animation? (send i is-checked?)) From 550314efff1fd42aa8e9017548c5e0d6eba7a48f Mon Sep 17 00:00:00 2001 From: Lehi Toskin Date: Sat, 11 Feb 2017 18:39:39 -0800 Subject: [PATCH 03/11] It werks! (testing needed) --- base.rkt | 15 +++- embed.rkt | 212 ++++++++++++++++++++++++++++-------------------------- main.rkt | 10 +-- 3 files changed, 126 insertions(+), 111 deletions(-) diff --git a/base.rkt b/base.rkt index 66ec111..7f2a740 100644 --- a/base.rkt +++ b/base.rkt @@ -1006,10 +1006,19 @@ (-> (listof path-string?) void?) (for ([path (in-list imgs)]) ; create and load the bitmap + (define ext (path-get-extension path)) (define thumb-bmp - (if (bytes=? (path-get-extension path) #".svg") - (load-svg-from-file path) - (read-bitmap path))) + (cond [(bytes=? ext #".svg") + (load-svg-from-file path)] + [(bytes=? ext #".flif") + (define dec (flif-create-decoder)) + (flif-decoder-decode-file! dec path) + (parameterize ([want-animation? #f]) + (define bmp (first (flif->list path dec))) + (flif-destroy-decoder! dec) + bmp)] + [else + (read-bitmap path)])) (define thumb-path (path->thumb-path path)) ; use pict to scale the image to 100x100 (define thumb-pct (bitmap thumb-bmp)) diff --git a/embed.rkt b/embed.rkt index e0af138..d7ab067 100644 --- a/embed.rkt +++ b/embed.rkt @@ -1,7 +1,8 @@ #lang racket/base ; embed.rkt ; embed tags into images that support it -(require file/sha1 +(require file/gunzip + file/gzip gif-image ; identifier conflict with xml (prefix-in gif: gif-image/gif-basics) @@ -177,6 +178,33 @@ GIF XMP keyword: #"XMP Data" with auth #"XMP" (define (flif-goto-marker in marker-bytes) (regexp-match-peek-positions (byte-regexp marker-bytes) in)) +; 128 +(define flif-separator #x80) + +; see https://github.com/FLIF-hub/FLIF/blob/master/src/flif-enc.cpp#L747-L758 +; for implementation details +(define (length->bytes num) + (let loop ([number num] + [done? #t]) + (cond [(< number flif-separator) + (if done? + (bytes number) + (bytes (+ number flif-separator)))] + [else + (define lsb (bitwise-and number (- flif-separator 1))) + (define n (arithmetic-shift number -7)) + (bytes-append + (loop n #f) + (loop lsb #t))]))) + +; modified from riff +(define (bytes->length bstr) + (for/fold ([result 0]) + ([byte (in-bytes bstr)]) + (if (< byte flif-separator) + (+ result byte) + (arithmetic-shift (+ result (- byte flif-separator)) 7)))) + #| Embedding stuff |# (define/contract (embed-support? img) @@ -192,36 +220,10 @@ GIF XMP keyword: #"XMP Data" with auth #"XMP" [(svg? img) (add-embed-svg! img taglist)])) ; adds the taglist to the existing tags -; if there are no existing tags, set them (define (add-embed-flif! flif taglist) - ; get the image pointer - (define image (flif-decoder-get-image (decoder) 0)) - (define old-xmp (flif-image-get-metadata image "eXmp")) (define old-tags (get-embed-tags flif)) (define reconciled (remove-duplicates (append old-tags taglist))) - (define xexpr (if (bytes=? old-xmp #"") - ; if the image has no xmp data, generate some - (make-xmp-xexpr taglist) - (set-dc:subject (string->xexpr (bytes->string/utf-8 old-xmp)) reconciled))) - (define bstr (string->bytes/utf-8 (xexpr->xmp xexpr))) - ; set the xmp inside the image pointer - (flif-image-set-metadata! image "eXmp" bstr) - ; re-encode and save the new flif - (define encoder (flif-create-encoder)) - (define encoded - (cond [(= (flif-decoder-num-images (decoder)) 1) - (flif-encoder-add-image! encoder image) - (flif-encoder-encode-memory encoder)] - [else - (for ([i (in-range (flif-decoder-num-images (decoder)))]) - (define img (flif-decoder-get-image (decoder i))) - (flif-encoder-add-image! encoder img)) - (flif-encoder-encode-memory encoder)])) - (flif-destroy-encoder! encoder) - (with-output-to-file flif - (λ () (display encoded)) - #:mode 'binary - #:exists 'truncate/replace)) + (set-embed-flif! flif reconciled)) ; adds taglist to the existing tags ; if there are no existing tags, set them @@ -281,67 +283,56 @@ GIF XMP keyword: #"XMP Data" with auth #"XMP" [(png? img) (set-xmp-png! img xmp-str)] [(svg? img) (set-xmp-svg! img xmp-str)])) -; -; TODO: -; If we're encoding, make sure to grab every option possible -; and set that in the encoder we've created. Alternatively, -; do like the other formats and forego the built-in metadata -; function so I don't have to painstakingly re-encode the -; image data every time I set the XMP. -; -(define (set-xmp-flif! flif xmp-str) - (define bstr (string->bytes/utf-8 xmp-str)) - ; in case we're running from the command-line - (cond - [(decoder) - (define image (flif-decoder-get-image (decoder) 0)) - (flif-image-set-metadata! image "eXmp" bstr) - ; re-encode and save the new flif - (define encoder (flif-create-encoder)) - (define encoded - (cond [(= (flif-decoder-num-images (decoder)) 1) - (flif-encoder-add-image! encoder image) - (flif-encoder-encode-memory encoder)] - [else - (for ([i (in-range (flif-decoder-num-images (decoder)))]) - (define img (flif-decoder-get-image (decoder) i)) - (flif-encoder-add-image! encoder img)) - (flif-encoder-encode-memory encoder)])) - (flif-destroy-encoder! encoder) - (with-output-to-file flif - (λ () (display encoded)) - #:mode 'binary - #:exists 'truncate/replace)] - [else - (define decoder (flif-create-decoder)) - (flif-decoder-decode-file! decoder flif) - (define image (flif-decoder-get-image decoder 0)) - ; re-encode and save the new flif - (define encoder (flif-create-encoder)) - (define encoded - (cond [(= (flif-decoder-num-images (decoder)) 1) - (flif-encoder-add-image! encoder image) - (flif-encoder-encode-memory encoder)] - [else - (for ([i (in-range (flif-decoder-num-images decoder))]) - (define img (flif-decoder-get-image (decoder i))) - (flif-encoder-add-image! encoder img)) - (flif-encoder-encode-memory encoder)])) - (flif-destroy-decoder! decoder) - (flif-destroy-encoder! encoder) - (with-output-to-file flif - (λ () (display encoded)) - #:mode 'binary - #:exists 'truncate/replace)])) +; do not re-encode the file every time we modify the xmp +(define (set-xmp-flif! flif xmp) + (define flif-bstr (file->bytes flif)) + (define flif-in (open-input-bytes flif-bstr)) + ; deflate the xmp metadata + (define xmp-bstr (if (bytes? xmp) xmp (string->bytes/utf-8 xmp))) + (define deflated-in (open-input-bytes xmp-bstr)) + (define deflated-out (open-output-bytes)) + (deflate deflated-in deflated-out) + ; add checksum + (define deflated-bstr + (bytes-append (get-output-bytes deflated-out) + (integer->integer-bytes (bytes-adler32 xmp-bstr) 4 #f #t))) + (define marker-lst + (let ([has-exmp? (flif-goto-marker flif-in #"eXmp")]) + (if has-exmp? + has-exmp? + (flif-goto-marker flif-in (bytes 0))))) + (close-input-port flif-in) + (define marker (first marker-lst)) + ; just before #"eXmp" + (define before (subbytes flif-bstr 0 (car marker))) + (define len-bstr + (let loop ([bstr #""] + [pos (cdr marker)]) + (define byte (bytes-ref flif-bstr pos)) + (if (< byte flif-separator) + (bytes-append bstr (bytes byte)) + (loop (bytes-append bstr (bytes byte)) (+ pos 1))))) + (define len (bytes->length len-bstr)) + ; skip up to len for after bytes + (define after (subbytes flif-bstr (+ (cdr marker) len 2))) + (with-output-to-file flif + (λ () (printf "~a~a~a~a~a" + before + #"eXmp" + (length->bytes (bytes-length deflated-bstr)) + deflated-bstr + after)) + #:mode 'binary + #:exists 'truncate/replace)) (define (set-embed-flif! flif taglist) - ; get the image pointer - (define image (flif-decoder-get-image (decoder) 0)) - (define old-xmp (flif-image-get-metadata image "eXmp")) - (define xexpr (if (bytes=? old-xmp #"") + (define old-xmp (get-embed-flif flif)) + (define xexpr (if (empty? old-xmp) ; if the image has no xmp data, generate some (make-xmp-xexpr taglist) - (set-dc:subject (string->xexpr (bytes->string/utf-8 old-xmp)) taglist))) + (set-dc:subject + (string->xexpr (first old-xmp)) + taglist))) (define xmp-str (xexpr->xmp xexpr)) (set-xmp-flif! flif xmp-str)) @@ -556,25 +547,41 @@ GIF XMP keyword: #"XMP Data" with auth #"XMP" [(png? img) (get-embed-png img)] [(svg? img) (get-embed-svg img)])) +; scan the FLIF and grab the metadata without using the +; library at all - no need to decode the image (define (get-embed-flif flif) - ; if we're calling from the command-line, we won't have - ; a proper decoder in place, so create a new one + (define flif-in (if (bytes? flif) + (open-input-bytes flif) + (open-input-file flif))) + (define marker-lst (flif-goto-marker flif-in #"eXmp")) (cond - [(decoder) - (define image (flif-decoder-get-image (decoder) 0)) - (define xmp (flif-image-get-metadata image "eXmp")) - (if (bytes=? xmp #"") - empty - (list (bytes->string/utf-8 xmp)))] - [else - (define dec (flif-create-decoder)) - (flif-decoder-decode-file! dec flif) - (define image (flif-decoder-get-image (decoder) 0)) - (define xmp (flif-image-get-metadata image "eXmp")) - (flif-destroy-decoder! dec) - (if (bytes=? xmp #"") - empty - (list (bytes->string/utf-8 xmp)))])) + [marker-lst + (define marker (first marker-lst)) + (define len-bstr + (let loop ([bstr #""] + [pos (cdr marker)]) + (define byte (peek-bytes 1 pos flif-in)) + (if (< (bytes-ref byte 0) flif-separator) + (bytes-append bstr byte) + (loop (bytes-append bstr byte) (+ pos 1))))) + (define len (bytes->length len-bstr)) + (define xmp-deflated (peek-bytes len (+ (cdr marker) 2) flif-in)) + (close-input-port flif-in) + ; inflate the data + (define inflate-in (open-input-bytes xmp-deflated)) + (define inflate-out (open-output-bytes)) + ; catch possible malformed compressed data + (with-handlers ([exn:fail? (λ (e) + (eprintf "~a\n" (exn-message e)) + (close-input-port inflate-in) + (close-output-port inflate-out) + empty)]) + (inflate inflate-in inflate-out)) + (define inflated (get-output-bytes inflate-out)) + (close-input-port inflate-in) + (close-output-port inflate-out) + (list (bytes->string/utf-8 inflated))] + [else empty])) ; retrieve the XMP data located inside the iTXt block(s) (define (get-embed-png png) @@ -758,7 +765,6 @@ GIF XMP keyword: #"XMP Data" with auth #"XMP" ((xmlns:rdf "http://www.w3.org/1999/02/22-rdf-syntax-ns#")) (rdf:Description ((rdf:about "") - (xmlns:Iptc4xmpCore "http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/") (xmlns:dc "http://purl.org/dc/elements/1.1/") (xmlns:xmp "http://ns.adobe.com/xap/1.0/") (xmlns:xmpRights "http://ns.adobe.com/xap/1.0/rights/") diff --git a/main.rkt b/main.rkt index 498966b..105c559 100755 --- a/main.rkt +++ b/main.rkt @@ -249,7 +249,7 @@ (unless (zero? len) (for ([sr (in-list search-sorted)]) (if (null-flag) - (printf "~a" (bytes-append (string->bytes/utf-8 sr) #"\0")) + (printf "~a" (bytes-append (string->bytes/utf-8 sr) (bytes 0))) (printf "~a~n" sr))) (when (verbose?) (printf "Found ~a results for tags ~v~n" len (tags-to-search))))] @@ -266,7 +266,7 @@ (unless (zero? len) (for ([sr (in-list final-sorted)]) (if (null-flag) - (printf "~a" (bytes-append (string->bytes/utf-8 sr) #"\0")) + (printf "~a" (bytes-append (string->bytes/utf-8 sr) (bytes 0))) (printf "~a~n" sr))) (when (verbose?) (printf "Found ~a results without tags ~v~n" len (tags-to-exclude))))] @@ -288,7 +288,7 @@ (define exclude-sorted (sort (map path->string exclude) stringbytes/utf-8 ex) #"\0")) + (printf "~a" (bytes-append (string->bytes/utf-8 ex) (bytes 0))) (printf "~a~n" ex))) (when (verbose?) (printf "Found ~a results for tags ~v, excluding tags ~v~n" @@ -302,7 +302,7 @@ (define taglist (image-taglist absolute-path)) (for ([tag (in-list taglist)]) (if (null-flag) - (printf "~a" (bytes-append (string->bytes/utf-8 tag) #"\0")) + (printf "~a" (bytes-append (string->bytes/utf-8 tag) (bytes 0))) (printf "~a~n" tag)))))] [(show-xmp?) (for ([img (in-list args)]) @@ -313,7 +313,7 @@ (define xmp (get-embed-xmp absolute-path)) (for ([str (in-list xmp)]) (if (null-flag) - (printf "~a" (bytes-append (string->bytes/utf-8 str) #"\0")) + (printf "~a" (bytes-append (string->bytes/utf-8 str) (bytes 0))) (printf "~a~n" str)))))] ; default to 0 if there is no recorded xmp:Rating [(show-rating?) From 48f5aaf195fbdf272048d0798572ff7de7feb3f0 Mon Sep 17 00:00:00 2001 From: Lehi Toskin Date: Sat, 11 Feb 2017 21:48:52 -0800 Subject: [PATCH 04/11] flif has or does not yet have eXmp chunk --- embed.rkt | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/embed.rkt b/embed.rkt index d7ab067..ef0e36b 100644 --- a/embed.rkt +++ b/embed.rkt @@ -283,6 +283,10 @@ GIF XMP keyword: #"XMP Data" with auth #"XMP" [(png? img) (set-xmp-png! img xmp-str)] [(svg? img) (set-xmp-svg! img xmp-str)])) +; TODO: if there is no existing eXmp chunk, seek until +; just after FLIF header information, because it's +; possible that any existing eXif chunks could have +; a #"\0" in the compressed data ; do not re-encode the file every time we modify the xmp (define (set-xmp-flif! flif xmp) (define flif-bstr (file->bytes flif)) @@ -296,25 +300,32 @@ GIF XMP keyword: #"XMP Data" with auth #"XMP" (define deflated-bstr (bytes-append (get-output-bytes deflated-out) (integer->integer-bytes (bytes-adler32 xmp-bstr) 4 #f #t))) + (define has-exmp? (flif-goto-marker flif-in #"eXmp")) (define marker-lst - (let ([has-exmp? (flif-goto-marker flif-in #"eXmp")]) - (if has-exmp? - has-exmp? - (flif-goto-marker flif-in (bytes 0))))) + (if has-exmp? + has-exmp? + (flif-goto-marker flif-in (bytes 0)))) (close-input-port flif-in) (define marker (first marker-lst)) ; just before #"eXmp" (define before (subbytes flif-bstr 0 (car marker))) (define len-bstr - (let loop ([bstr #""] - [pos (cdr marker)]) - (define byte (bytes-ref flif-bstr pos)) - (if (< byte flif-separator) - (bytes-append bstr (bytes byte)) - (loop (bytes-append bstr (bytes byte)) (+ pos 1))))) + (if has-exmp? + (let loop ([bstr #""] + [pos (cdr marker)]) + (define byte (bytes-ref flif-bstr pos)) + (if (< byte flif-separator) + (bytes-append bstr (bytes byte)) + (loop (bytes-append bstr (bytes byte)) (+ pos 1)))) + #"")) (define len (bytes->length len-bstr)) - ; skip up to len for after bytes - (define after (subbytes flif-bstr (+ (cdr marker) len 2))) + ; skip up to len for after-bytes + ; (if there is no existing eXmp chunk, seek until just before the first #"\0") + (define after (subbytes flif-bstr (+ (if (zero? len) + (car marker) + (cdr marker)) + len + (bytes-length len-bstr)))) (with-output-to-file flif (λ () (printf "~a~a~a~a~a" before From 8c3b773b021c18cfe97366386372b4ec1b1e5033 Mon Sep 17 00:00:00 2001 From: Lehi Toskin Date: Sat, 11 Feb 2017 23:14:15 -0800 Subject: [PATCH 05/11] If no eXmp, skip header bytes --- embed.rkt | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/embed.rkt b/embed.rkt index ef0e36b..a18fc3e 100644 --- a/embed.rkt +++ b/embed.rkt @@ -178,6 +178,29 @@ GIF XMP keyword: #"XMP Data" with auth #"XMP" (define (flif-goto-marker in marker-bytes) (regexp-match-peek-positions (byte-regexp marker-bytes) in)) +; seek until just after the header information and +; take into account the possible nb_frames varint +(define (flif-skip-header bstr) + ; start at 6: FLIF + info + bpc + (let loop ([pos 6] + [section 0]) + (cond [(= section 3) pos] + [(= section 2) + ; does nb_frames exist? (is the image animated?) + (define info (flif-read-info-from-memory bstr)) + (define num (flif-info-num-images info)) + (flif-destroy-info! info) + (if (= num 1) + ; still image + (loop pos (+ section 1)) + (loop (+ pos (bytes-length (length->bytes num))) (+ section 1)))] + [else + ; scan through width+height + (define byte (bytes-ref bstr pos)) + (if (< byte flif-separator) + (loop (+ pos 1) (+ section 1)) + (loop (+ pos 1) section))]))) + ; 128 (define flif-separator #x80) @@ -283,10 +306,6 @@ GIF XMP keyword: #"XMP Data" with auth #"XMP" [(png? img) (set-xmp-png! img xmp-str)] [(svg? img) (set-xmp-svg! img xmp-str)])) -; TODO: if there is no existing eXmp chunk, seek until -; just after FLIF header information, because it's -; possible that any existing eXif chunks could have -; a #"\0" in the compressed data ; do not re-encode the file every time we modify the xmp (define (set-xmp-flif! flif xmp) (define flif-bstr (file->bytes flif)) @@ -300,11 +319,13 @@ GIF XMP keyword: #"XMP Data" with auth #"XMP" (define deflated-bstr (bytes-append (get-output-bytes deflated-out) (integer->integer-bytes (bytes-adler32 xmp-bstr) 4 #f #t))) + ; piece everything together (define has-exmp? (flif-goto-marker flif-in #"eXmp")) (define marker-lst - (if has-exmp? - has-exmp? - (flif-goto-marker flif-in (bytes 0)))) + (cond [has-exmp? has-exmp?] + [else + (define header (flif-skip-header flif-bstr)) + (list (cons header (+ header 1)))])) (close-input-port flif-in) (define marker (first marker-lst)) ; just before #"eXmp" From c2f59fd830fe00d97f359163ec08f1c7ec3bb902 Mon Sep 17 00:00:00 2001 From: Lehi Toskin Date: Sat, 11 Feb 2017 23:45:49 -0800 Subject: [PATCH 06/11] Reload FLIF if we want animation --- frame.rkt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frame.rkt b/frame.rkt index d3577d6..b1b77d1 100644 --- a/frame.rkt +++ b/frame.rkt @@ -451,14 +451,14 @@ (new checkable-menu-item% [parent ivy-menu-bar-view] [label "&Animation"] - [help-string "Animate GIFs, if possible."] + [help-string "Animate image, if possible."] [callback (λ (i e) (want-animation? (send i is-checked?)) - (when (and (not (equal? (image-path) root-path)) - (gif? (image-path)) - (gif-animated? (image-path))) - (collect-garbage 'incremental) - (load-image (image-path))))])) + (when (and (not (equal? (image-path) root-path)) + (or (and (gif? (image-path)) + (gif-animated? (image-path))) + (flif-animated? (image-path)))) + (load-image (image-path))))])) (define ivy-menu-bar-view-tag-browser (new menu-item% From b9023f642b71cef4d708aaa6dd7d7ca32accefd7 Mon Sep 17 00:00:00 2001 From: Lehi Toskin Date: Sun, 12 Feb 2017 02:40:59 -0800 Subject: [PATCH 07/11] Replace flif-info with flif-animated? --- README.md | 2 +- embed.rkt | 15 +++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 0fbc30f..88b9fa1 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ tracked solely by the application. - [gif-image](https://github.com/lehitoskin/gif-image) - [png-image](https://github.com/lehitoskin/png-image) - [racquel](https://github.com/brown131/racquel) -- [riff](https://github.com/lehitoskin/riff) (which uses FLIF) +- [riff](https://github.com/lehitoskin/riff) (which uses FLIF, see below) - [rsvg](https://github.com/takikawa/rsvg) (which uses librsvg) - [sugar](https://github.com/mbutterick/sugar) - [txexpr](https://github.com/mbutterick/txexpr) diff --git a/embed.rkt b/embed.rkt index a18fc3e..59fd66c 100644 --- a/embed.rkt +++ b/embed.rkt @@ -186,14 +186,13 @@ GIF XMP keyword: #"XMP Data" with auth #"XMP" [section 0]) (cond [(= section 3) pos] [(= section 2) - ; does nb_frames exist? (is the image animated?) - (define info (flif-read-info-from-memory bstr)) - (define num (flif-info-num-images info)) - (flif-destroy-info! info) - (if (= num 1) - ; still image - (loop pos (+ section 1)) - (loop (+ pos (bytes-length (length->bytes num))) (+ section 1)))] + (cond [(flif-animated? bstr) + ; count nb_frames + (define byte (bytes-ref bstr pos)) + (if (< byte flif-separator) + (loop (+ pos 1) (+ section 1)) + (loop (+ pos 1) section))] + [else (loop pos (+ section 1))])] [else ; scan through width+height (define byte (bytes-ref bstr pos)) From 578b0e9715eb24e06b0303fd3da395c6669d5d8f Mon Sep 17 00:00:00 2001 From: Lehi Toskin Date: Sun, 12 Feb 2017 02:51:43 -0800 Subject: [PATCH 08/11] FLIFs are not progressively decoded --- README.md | 9 ++++----- doc/ivy.1.bz2 | Bin 2772 -> 2765 bytes doc/ivy.html | 4 ++-- doc/ivy.md | 9 ++++----- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 88b9fa1..1e778d3 100644 --- a/README.md +++ b/README.md @@ -119,15 +119,14 @@ an image path and selecting "Edit Tags" from the menu. ### Animated GIF Support Animated GIF support is currently marked as experimental. To animate a GIF, -select View -> GIF Animation. Due to the unstable nature of the related code, -know that some GIF's may not load properly or at all. +select View -> Animation. Due to the unstable nature of the related code, know +that some GIF's may not load properly or at all. ### FLIF Support [FLIF](https://github.com/FLIF-hub/FLIF) support is marked as experimental! To -load a FLIF file in Ivy, simply open like any other image. Ivy will load the -image progressively and a little percentage meter will appear prepended to -the image's filename in the title of the frame. +load a FLIF file in Ivy, simply open like any other image. Currently, the FLIF +decoder is not optimized, so decoding takes much longer than for other formats. ### Embedding Tags as XMP metadata diff --git a/doc/ivy.1.bz2 b/doc/ivy.1.bz2 index 114adf58c667487e441b9756098813a50a3eea47..d64c22d1be834fbb83fd17c3aeb01826591f3343 100644 GIT binary patch literal 2765 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 Browser

Ivy comes with a tag browser mode (available from the View menu or with Ctrl-B) so that you may view every tag category and its contents. Clicking on a tag will show the images you have tagged. Clicking on the image will show a thumbnail preview which is itself clickable. Clicking on the thumbnail button will tell Ivy to load the source image for further viewing.

From the tag browser it is also possible to delete entire tag categories or rename them. Also, it is possible to edit the taglist of an image by clicking on an image path and selecting "Edit Tags" from the menu.

Animated GIF Support

-

Animated GIF support is currently marked as experimental. To animate a GIF, select View -> GIF Animation. Due to the unstable nature of the related code, know that some GIF's may not load properly or at all.

+

Animated GIF support is currently marked as experimental. To animate a GIF, select View -> Animation. Due to the unstable nature of the related code, know that some GIF's may not load properly or at all.

FLIF Support

-

FLIF support is marked as experimental! To load a FLIF file in Ivy, simply open like any other image. Ivy will load the image progressively and a little percentage meter will appear prepended to the image's filename in the title of the frame.

+

FLIF support is marked as experimental! To load a FLIF file in Ivy, simply open like any other image. Currently, the FLIF decoder is not optimized, so decoding takes much longer than for other formats.

Embedding Tags as XMP metadata

Ivy embeds the taglist as XMP metadata in the file itself (if that file supports XMP metadata). That way if you move your images around, the tags will stay the same. However, the information in the database will then be out of date, so it is recommended that if you move files around, utilize Ivy's command-line interface with the switch -M to ensure the changes are tracked.

OPTIONS

diff --git a/doc/ivy.md b/doc/ivy.md index 8c1c58f..9fede43 100644 --- a/doc/ivy.md +++ b/doc/ivy.md @@ -81,15 +81,14 @@ an image path and selecting "Edit Tags" from the menu. ## Animated GIF Support Animated GIF support is currently marked as experimental. To animate a GIF, -select View -> GIF Animation. Due to the unstable nature of the related code, -know that some GIF's may not load properly or at all. +select View -> Animation. Due to the unstable nature of the related code, know +that some GIF's may not load properly or at all. ## FLIF Support FLIF support is marked as experimental! To load a FLIF file in Ivy, simply open -like any other image. Ivy will load the image progressively and a little -percentage meter will appear prepended to the image's filename in the title of -the frame. +like any other image. Currently, the FLIF decoder is not optimized, so decoding +takes much longer than for other formats. ## Embedding Tags as XMP metadata From de2a66628192490952caab3250e82776e76d28e4 Mon Sep 17 00:00:00 2001 From: Lehi Toskin Date: Sun, 12 Feb 2017 14:23:10 -0800 Subject: [PATCH 09/11] Check if FLIF is animated --- base.rkt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base.rkt b/base.rkt index 7f2a740..ee657d8 100644 --- a/base.rkt +++ b/base.rkt @@ -665,7 +665,7 @@ (+ (get-index img (pfs)) 1) (length (pfs))))] ; load animated flif - [(and (want-animation?) (flif? img)) + [(and (want-animation?) (flif? img) (flif-animated? img)) (cumulative? #f) (decoder (flif-create-decoder)) ; progressive decoding From 7190f49a8404e799f31ae3e26962ada31e601d35 Mon Sep 17 00:00:00 2001 From: Lehi Toskin Date: Sun, 12 Feb 2017 18:43:50 -0800 Subject: [PATCH 10/11] Fix flif->list arity error --- base.rkt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base.rkt b/base.rkt index ee657d8..1dc12c4 100644 --- a/base.rkt +++ b/base.rkt @@ -1014,7 +1014,7 @@ (define dec (flif-create-decoder)) (flif-decoder-decode-file! dec path) (parameterize ([want-animation? #f]) - (define bmp (first (flif->list path dec))) + (define bmp (first (flif->list dec))) (flif-destroy-decoder! dec) bmp)] [else From 44804ac657bdecc179acb28c8c88ae36a34c015e Mon Sep 17 00:00:00 2001 From: Lehi Toskin Date: Sun, 12 Feb 2017 19:30:32 -0800 Subject: [PATCH 11/11] May as well add XBM and XPM --- base.rkt | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/base.rkt b/base.rkt index 1dc12c4..d4a3fee 100644 --- a/base.rkt +++ b/base.rkt @@ -56,8 +56,19 @@ (define image-bmp (make-bitmap 50 50)) ; directory containing the currently displayed image (define image-dir (make-parameter (find-system-path 'home-dir))) -(define supported-extensions - '(".bmp" ".flif" ".gif" ".jpe" ".jpeg" ".JPEG" ".jpg" ".JPG" ".png" ".svg")) +; the only extensions ivy will accept - ignores everything else +(define supported-extensions '(".bmp" + ".flif" + ".gif" + ".jpe" + ".jpeg" + ".JPEG" + ".jpg" + ".JPG" + ".png" + ".svg" + ".xbm" + ".xpm")) ; gif/flif stuff ; listof pict? (define image-lst-master empty)