Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions builtin-programs/draw/image.folk
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
When the image library is /imageLib/ &\
the jpeg library is /jpegLib/ &\
the png library is /pngLib/ {
the png library is /pngLib/ &\
the gif library is /gifLib/ {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how much I like adding yet another dependency to image.folk. There isn't a good story for graceful degradation here.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could split it into independent handlers for gif/png/jpeg that all check if the path applies to them and return if not?

set cc [C]
$cc extend $imageLib

Expand Down Expand Up @@ -65,17 +66,26 @@ When the image library is /imageLib/ &\
set impath $im
if {[string match "http*://*" $impath]} {
set im /tmp/[regsub -all {\W+} $impath "_"]
exec curl -o$im $impath
if {![file exists $im]} {
exec curl -s -L -o$im $impath
}
}
if {[string match "*jpg" $im] ||
[string match "*jpeg" $im] ||
[string match "*png" $im]} {
[string match "*png" $im] ||
[string match "*gif" $im]} {
set path [expr {[string index $im 0] eq "/" ?
$im : "$::env(HOME)/folk-images/$im"}]
if {![file exists $path]} {
set path [file join [pwd] $im]
}
if {[string match "*jpg" $im] || [string match "*jpeg" $im]} {
set im [$jpegLib loadJpeg $path]
} else {
} elseif {[string match "*png" $im]} {
set im [$pngLib loadPng $path]
} else {
set gif [$gifLib loadGif $path]
set im [lindex [dict get $gif frames] 0]
}
$cacheLib cachePut $impath $im
}
Expand Down
84 changes: 84 additions & 0 deletions builtin-programs/image/animated-gif.folk
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
When the gif library is /gifLib/ {
set gifCache [dict create]

When /someone/ wishes /p/ displays gif /path/ with /...options/ &\
/p/ has resolved geometry /geom/ {

if {![dict exists $gifCache $path]} {
set path_resolved $path

# Support remote images via HTTP
if {[string match "http*://*" $path]} {
set path_resolved /tmp/[regsub -all {\W+} $path "_"].gif
if {![file exists $path_resolved]} {
puts stderr "gif: downloading $path to $path_resolved"
if {[catch {exec curl -s -L -o $path_resolved $path} err]} {
puts stderr "gif: download failed: $err"
return
}
}
}

if {![file exists $path_resolved]} {
set path_resolved_local "$::env(HOME)/folk-images/$path_resolved"
if {[file exists $path_resolved_local]} {
set path_resolved $path_resolved_local
}
}
if {![file exists $path_resolved]} {
# Fallback to current directory
set path_resolved_local [file join [pwd] $path_resolved]
if {[file exists $path_resolved_local]} {
set path_resolved $path_resolved_local
}
}

if {![file exists $path_resolved]} {
return
}

try {
dict set gifCache $path [$gifLib loadGif $path_resolved]
} on error {err} {
puts stderr "gif: error loading $path: $err"
return
}
}

set gif [dict get $gifCache $path]
set frames [dict get $gif frames]
set delays [dict get $gif delays]

set totalDuration 0
set cumulativeDelays [list]
foreach d $delays {
if {$d <= 10} { set d 100 }
incr totalDuration $d
lappend cumulativeDelays $totalDuration
}

if {[llength $frames] == 0} { return }

if {[llength $frames] == 1} {
Wish $p displays image [lindex $frames 0] with {*}$options
return
}

When the clock time is /t/ {
set ms [expr {int($t * 1000) % $totalDuration}]
set frameIdx 0
foreach cd $cumulativeDelays {
if {$ms < $cd} { break }
incr frameIdx
}
if {$frameIdx >= [llength $frames]} { set frameIdx 0 }
set im [lindex $frames $frameIdx]

Wish $p displays image $im with {*}$options
}
}

When /someone/ wishes /p/ displays gif /path/ {
Wish $p displays gif $path with scale 1.0
}
}
52 changes: 52 additions & 0 deletions builtin-programs/image/gif-lib.folk
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
When the image library is /imageLib/ {
set cc [C]
$cc extend $imageLib
$cc cflags -I[pwd]/vendor/gifdec
$cc endcflags [pwd]/vendor/gifdec/gifdec.c
$cc include "gifdec.h"
$cc include <stdlib.h>

$cc proc loadGif {char* filename} Jim_Obj* {
gd_GIF *gif = gd_open_gif(filename);
if (!gif) {
FOLK_ERROR("loadGif: Failed to open %s", filename);
}

Jim_Obj *framesList = Jim_NewListObj(interp, NULL, 0);
Jim_Obj *delaysList = Jim_NewListObj(interp, NULL, 0);

while (gd_get_frame(gif)) {
Image im;
im.width = gif->width;
im.height = gif->height;
im.components = 3;
im.bytesPerRow = im.width * 3;
im.data = malloc(im.bytesPerRow * im.height);
im.uniq = 0;

gd_render_frame(gif, im.data);

// Box the Image struct into a Tcl object
Jim_Obj *imObj;
$[$cc ret Image imObj im]
Jim_ListAppendElement(interp, framesList, imObj);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am sort of uncomfortable with how little I understand this (especially line 31). It works but I'd like to understand the mechanisms behind $cc ret a bit better.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a pretty deep cut -- I think I originally made ret and argtype purely for internal use in the C compiler library (to generate function prologues/epilogues). ret should generate Tcl code to turn Image into Jim_Obj (and assign it into imObj). It depends on the rtype already being defined in $cc, which happens when you extend imageLib.


// gifdec delay is in 1/100s of a second. Convert to milliseconds.
Jim_ListAppendElement(interp, delaysList, Jim_NewIntObj(interp, gif->gce.delay * 10));
}

uint16_t w = gif->width;
uint16_t h = gif->height;
gd_close_gif(gif);

Jim_Obj *result = Jim_NewDictObj(interp, NULL, 0);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be more elegant to define this as a struct Gif and return that on the stack instead of having the Tcl encoding inline here, I think

Jim_DictAddElement(interp, result, Jim_NewStringObj(interp, "frames", -1), framesList);
Jim_DictAddElement(interp, result, Jim_NewStringObj(interp, "delays", -1), delaysList);
Jim_DictAddElement(interp, result, Jim_NewStringObj(interp, "width", -1), Jim_NewIntObj(interp, w));
Jim_DictAddElement(interp, result, Jim_NewStringObj(interp, "height", -1), Jim_NewIntObj(interp, h));
return result;
}

set gifLib [$cc compile]
Claim the gif library is $gifLib
}
Loading