How to -draw text and -shadow it in ImageMagick? - text

I'm printing some text on an image with convert and I would like to decorate the text with a black shadow, I tried -blur or -gaussian but I cannot apply to the text, it is applied to the background image only.
I need to use -draw command and not -annotate.
And this is the code I need to update for shadowing
-font "geometricslab703bt-bold-webfont.ttf" -fill White -pointsize 18 -draw "rotate -4 text 350,250 '---- mijn ideale ----'"
thanks in advance

A better, more flexible, way to work with text shadow is to render the shadow on a new layer. This method will allow you to manipulate the shadow-text, as needed, without affecting the background. Finally draw the actual text on top of the shadow after adjusting any geometric offsetting. Here's an example:
convert -size 280x100 pattern:SMALLFISHSCALES \
\( xc:transparent -font "Menlo" -pointsize 32 -fill black -draw "rotate -4 text 20,60 'ImageMagick'" -blur 0x1 \) \
-geometry +2+2 -composite \
-font "Menlo" -fill white -pointsize 32 -draw "rotate -4 text 20,60 'ImageMagick'" \
example.png
The escaped braces "\( \)" will create a new sub-image; which, will be applied to the background with -composite flag.
This solution is a little bit more labor intensive, but keeps all your effects isolated.

You can use caption to draw text, and make a shadow layer with clone then merge the two layer.
convert logo: -resize 40%x40 \
\( -size "80x40" -background none -gravity west -fill green caption:"Caption text" \
\( +clone -background navy -shadow 80x3+5+5 \) +swap -background none -layers merge +repage \) -composite \
\( -size "80x40" -background none -gravity east -fill green caption:"Caption text" \
\( +clone -background red -shadow 80x3+5+5 \) +swap -background none -layers merge +repage \) -composite \
out.png

#emcconville's answer have helped me with this transparency implementation:
if [ ! -f ../watermark_template.png ]; then convert -size 511x81 xc:transparent ../watermark_template.png; fi
convert ../watermark_template.png -size 511x81 \
\( xc:transparent -font "Arial" -pointsize 20 -fill black -draw "text 40,35 '$osname $osversion Insider Preview'" -blur 0x1 \) \
-geometry +2+2 -composite \
-font "Arial" -fill white -pointsize 20 -draw "text 40,35 '$osname $osversion Insider Preview'" \
../watermark.png
#- from https://stackoverflow.com/a/20861391/5623661
convert ../watermark.png -size 511x81 \
\( xc:transparent -font "Arial" -pointsize 20 -fill black -draw "text 40,60 'Evaluation compilation. Build $osbuild $osbuildcodename'" -blur 0x1 \) \
-geometry +2+2 -composite \
-font "Arial" -fill white -pointsize 20 -draw "text 40,60 'Evaluation compilation. Build $osbuild $osbuildcodename'" \
../watermark.png
convert ../watermark.png -size 511x81 \
\( xc:transparent -font "Arial" -pointsize 16 -fill black -draw "text 180,76 'Build attempt $specialbuildattempt'" -blur 0x1 \) \
-geometry +2+2 -composite \
-font "Arial" -fill white -pointsize 16 -draw "text 180,76 'Build attempt $specialbuildattempt'" \
../watermark.png
Which (locally) results in:
PS: this will vary depending on how variables are declared.
You can put your own custom variables so the image generation become dynamic and can be used programmatically.
Also, this implementation doesn't require, necessarilly, having an existing image to work from.

Related

How to convert raw YUV image to jpg

I have a raw image that was taken with v4l2-ctl after the camera had been setup like:
# media-ctl -d /dev/media0 -l "'rzg2l_csi2 10830400.csi2':1 -> 'CRU output':0 [1]"
# media-ctl -d /dev/media0 -V "'rzg2l_csi2 10830400.csi2':1 [fmt:UYVY8_2X8/1280x960 field:none]"
# media-ctl -d /dev/media0 -V "'ov5645 0-003c':0 [fmt:UYVY8_2X8/1280x960 field:none]"
and then the picture got snapped with:
# v4l2-ctl --device /dev/video0 --stream-mmap --stream-to=frame.raw --stream-count=1
now I've tried multiple methods to convert this into a jpeg but nothing seems to yield the expected output
the raw file can be downloaded here: https://drive.google.com/file/d/1VqXnrJDYbzdtSsWfTlm2mX9rl1-Rl_7F/view?usp=sharing
I tried out the following command:
convert -verbose -size 1280x960 UYVY:frame.raw frame.bmp
which I found on Converting from YUV(UYVY) to RGB using imagemagick
but it doesn't do the trick
Your frame is 2457600 bytes and your pixel dimensions are 1280x960, so you have:
bits per pixel = 2457600 * 8 / (1280 * 960) = 16
You can get a list of the pixel formats that ffmpeg supports using:
ffmpeg -pix_fmts 2> /dev/null
Sample Output
FLAGS NAME NB_COMPONENTS BITS_PER_PIXEL
-----
IO... yuv420p 3 12
IO... yuyv422 3 16
IO... rgb24 3 24
IO... bgr24 3 24
IO... yuv422p 3 16
IO... yuv444p 3 24
IO... yuv410p 3 9
...
...
That means you can get a list of pixel formats that contain Y, U and V with 16 bits per pixel like this:
ffmpeg -pix_fmts 2> /dev/null | awk '/y/ && /u/ && /16$/ {print}'
IO... yuyv422 3 16
IO... yuv422p 3 16
IO... yuvj422p 3 16
IO... uyvy422 3 16
IO... yuv440p 3 16
IO... yuvj440p 3 16
IO... yvyu422 3 16
Now you can run a loop, iterating over all the 16-bit per pixel YUV formats and see what ffmpeg makes of your image - naming each result after the format so you can identify which is which:
ffmpeg -pix_fmts 2> /dev/null |
awk '/y/ && /u/ && /16$/ {print $2}' |
while read f; do
ffmpeg -y -s:v 1280x960 -pix_fmt $f -i frame.raw $f.jpg
done
That gives you these files:
-rw-r--r-- 1 mark staff 304916 3 Feb 09:38 yuv440p.jpg
-rw-r--r-- 1 mark staff 227123 3 Feb 09:38 yuvj422p.jpg
-rw-r--r-- 1 mark staff 39543 3 Feb 09:38 yuyv422.jpg
-rw-r--r-- 1 mark staff 39545 3 Feb 09:38 yvyu422.jpg
And I guess that yuyv422.jpg is your image, so that means you can extract it with:
ffmpeg -y -s:v 1280x960 -pix_fmt yuyv422 -i frame.raw result.jpg
If you wanted to do that with ImageMagick, you could do something like this:
#!/bin/bash
python3 <<EOF
import numpy as np
h, w = 960, 1280
# Load raw file into Numpy array
raw = np.fromfile('frame.raw', np.uint8)
raw[0::2].tofile('Y') # Starting at the 1st byte, write every 2nd byte to file "Y"
raw[1::4].tofile('U') # Starting at the 2nd byte, write every 4th byte to file "U"
raw[3::4].tofile('V') # Starting at the 3rd byte, write every 4th byte to file "V"
EOF
# Load the Y channel, then the U and V channels forcibly resizing them, then combine and go to sRGB
magick -depth 8 -size 1280x960 gray:Y \
\( -size 640x960 gray:U gray:V -resize 1280x960\! \) \
-set colorspace YUV -combine -colorspace sRGB result.jpg
If yo don't like/have Python, that part can be replaced with some basic C as follows:
#include <stdint.h>
#include <stdio.h>
// Split YUYV file called "frame.raw" into separate channels with filenames "Y", "U" and "V"
// Compile with: clang -O3 splitter.c -o splitter
int main(){
FILE *in, *Y, *U, *V;
uint8_t buffer[4];
size_t bytesRead;
// Open input file and 1 output file per channel
in = fopen("frame.raw", "rb");
Y = fopen("Y", "wb");
U = fopen("U", "wb");
V = fopen("V", "wb");
// read up to sizeof(buffer) bytes
while ((bytesRead = fread(buffer, 1, sizeof(buffer), in)) > 0)
{
fputc(buffer[0], Y);
fputc(buffer[1], U);
fputc(buffer[2], Y);
fputc(buffer[3], V);
}
}
Having had so much fun doing ffmpeg, Python, and C versions, I thought I'd try just doing it in the shell - converting bytes to lines and so I could pick alternate lines instead of alternate bytes. This works the same as the above:
#!/bin/bash
# Build JPEG image from YUYV image with packed bytes in order YUYVYUYV...
# Use "xxd" to convert bytes into lines, then extract alternate lines - which is easier than extracting bytes
H=960
W=1280
INPUT="frame.raw"
# Take top byte of every uint16 and put into "Y.pgm"
xxd -c1 -p "$INPUT" | sed -n 'p;n' | xxd -r -p | magick -size ${W}x${H} -depth 8 gray:- Y.pgm
# Take bottom byte of every 2nd uint16, starting at the 1st, resize up to full width and put into "U.pgm"
xxd -c1 -p "$INPUT" | sed -n 'n;p' | sed -n 'p;n' | xxd -r -p | magick -size $((W/2))x${H} -depth 8 gray:- -resize ${W}x${H}\! U.pgm
# Take bottom byte of every 2nd uint16, starting at the 2nd, resize up to full width and put into "V.pgm"
xxd -c1 -p "$INPUT" | sed -n 'n;p' | sed -n 'n;p' | xxd -r -p | magick -size $((W/2))x${H} -depth 8 gray:- -resize ${W}x${H}\! V.pgm
# Load the 3 channels, combine and convert to JPEG
magick {Y,U,V}.pgm -set colorspace YUV -combine -colorspace sRGB result.jpg
# Remove litter
rm {Y,U,V}.pgm
As regards colour cast removal, as I said in the comments, the " normal" way, AFAIK, is to get the average colour of the image and invert its Hue then blend that "negated cast" back with the original image to offset the original colour cast. Here is a crude attempt - if anyone knows better please ping me!
Step 1: Get average colour cast
magick result.jpg -resize 1x1\! cast.png
Step 2: Invert the cast
magick cast.png -modulate 100,100,0 correction.png
Step 3: Blend the original with the correction and brighten maybe
magick result.jpg correction.png -define compose:args=50,50 -compose blend -composite -auto-level result.jpg
Here are the original and corrected versions:
Obviously you can change the percentages for different degrees of "correction".

Script is not working. What am I doing wrong?

First off I found this guide, which details exactly what I need.
https://imagemagick.org/script/connected-components.php
For the life of me I cannot get this to work. Anyone have any idea?
I've tried a bunch of variations of the scripts listed in the guide.
Also when I run convert /var/www/mailtovoice/audrey/sean_look_grey.png -define connected-components:verbose=true -connected-components 8 /var/www/mailtovoice/audrey/sean_look4.png
I get 1000s of objects. When I converted it to an image with just 3 objects I get 100s.
Mark has the right idea, but the solution is much simpler than he posted, since ImageMagick -connected-components can do the filtering and output directly.
input:
Unix line endings (for windows use ^ rather than \ )
convert image.png \
-define connected-components:area-threshold=100 \
-define connected-components:mean-color=true \
-connected-components 4 \
result.png
The method suggested by Fred (#fmw42) is far simpler and preferable to that shown in this answer, so all but die-hard enthusiasts should use Fred's answer. Rather than delete mine, I will leave it showing as it could form the basis for other more demanding/involved processing.
This is a rather funny way to do it... find all the blobs. i.e. connected components:
convert spotty.png -define connected-components:verbose=true -connected-components 4 null:
which gives you something like this but with 2,000+ lines:
Objects (id: bounding-box centroid area mean-color):
0: 860x482+0+0 431.5,239.7 405738 gray(0)
800: 43x263+252+219 265.9,350.5 2458 gray(255)
2: 21x226+276+0 288.9,111.2 1540 gray(255)
2216: 5x16+107+445 109.3,452.9 65 gray(255)
910: 7x15+276+228 279.0,234.5 63 gray(255)
491: 7x14+651+150 654.1,156.6 54 gray(255)
1207: 7x9+735+282 737.9,285.8 53 gray(255)
2313: 6x9+147+457 149.6,460.9 48 gray(255)
985: 8x9+754+238 757.3,242.0 48 gray(255)
...
...
Now look for all the ones with a size (second-to-last field) less than 1000 using awk and print the region:
convert spotty.png \
-define connected-components:verbose=true \
-connected-components 4 null: |
awk -v thresh=1000 'NR>1 && $(NF-1)<thresh{print " -region " $2 " -colorize 100%"}'
Output
-region 5x16+107+445 -colorize 100%
-region 7x15+276+228 -colorize 100%
-region 7x14+651+150 -colorize 100%
-region 7x9+735+282 -colorize 100%
...
...
Now reload the original image, set the fill colour for colorised regions to red and regenerate the list of regions to be filled exactly as above:
convert spotty.png -fill red $(convert spotty.png -define connected-components:verbose=true -connected-components 4 null: | awk -v thresh=1000 'NR>1 && $(NF-1)<thresh{print " -region " $2 " -colorize 100%"}' ) result.png
The command generated boils down to:
convert spotty.png -threshold 50% -fill red \
-region 56x16+107+445 -colorize 100% \
-region 70x15+276+228 -colorize 100% \
-region ... -colorize 100% \
...
...
result.png

find a replace multiple strings in a matching code block using sed

Please any help on this using Linux "sed" will be appreciated.
I have the code block as below in a file. Looking to extract a block starting with "aa.bb.cc.config=" and end with "}". From the extracted block, find the strings ["bb11:11211"] ["ff5rt:11211"] and delete the matching lines from the file.
asdf={
ddd, \
fff, \
ggg \
}
ghjk
wwww
aa.bb.cc.config={"List": [ \
"aa11:222", \
"bb11:11211", \
"cc22ss:11211", \
"dd33ss:11211", \
"ff5rt:11211", \
"gg6hj:11211", \
], "dd": true, "ff": 0, "hh": 0, "jj": 10, "kk": 0 }
vsdfsdf
ewqewqe
OUTPUT:
asdf={
ddd, \
fff, \
ggg \
}
ghjk
wwww
aa.bb.cc.config={"List": [ \
"aa11:222", \
"cc22ss:11211", \
"dd33ss:11211", \
"gg6hj:11211", \
], "dd": true, "ff": 0, "hh": 0, "jj": 10, "kk": 0 }
vsdfsdf
ewqewqe
Thanks in Advance!!
sed approach:
sed '/^aa\.bb\.cc\.config=/,/}$/{ /\"\(bb11\|ff5rt\):11211\"/d; }' file
/^aa.bb.cc.config=/,/}$/ - address range, considering lines from /^aa.bb.cc.config=/ to /}$/
/\"\(bb11\|ff5rt\):11211\"/d; - delete lines matched the pattern within captured pattern space
The same using extended regex -E:
sed -E '/^aa\.bb\.cc\.config=/,/\}$/{ /"(bb11|ff5rt):11211"/d; }' file
The output:
asdf={
ddd, \
fff, \
ggg \
}
ghjk
wwww
aa.bb.cc.config={"List": [ \
"aa11:222", \
"cc22ss:11211", \
"dd33ss:11211", \
"gg6hj:11211", \
], "dd": true, "ff": 0, "hh": 0, "jj": 10, "kk": 0 }
vsdfsdf
ewqewqe

how to create a modeless dialog in tcl/tk

I'm trying to make a modeless dialog, since whenever a dialog appears stops the real time process that is running in the main gui, after some reasearch i realize that the problem that is causing the real time part to stop is the "vwait" command in the dialog procedure. How could I make the my current dialog modeless so it doesn't affect the real time process in the back? Should I put the msgDialog in a different thread everytime i call the proc ? or what other way can I do it?
TCL CODE:
proc MsgDialog {w message type icon} \
{
if {![winfo exists $w]} {
set dialColor white
image create photo .alert -format PNG -file alertIcon.png -width 40
image create photo .question -format PNG -file questionicon.png
toplevel $w -borderwidth 2 -relief raised -background $dialColor
wm overrideredirect $w 1
set x [expr { ( [winfo vrootwidth $w] - 350 ) / 2 }]
set y [expr { ( [winfo vrootheight $w] - 190 ) / 2 }]
wm geometry $w 350x190+${x}+${y}
frame $w.msgPnl -relief flat -borderwidth 1 -background $dialColor -width 280 -height 140
place $w.msgPnl -x 0 -y 0
frame $w.imgPnl -relief flat -borderwidth 1 -background $dialColor -width 50 -height 140
place $w.imgPnl -x 285 -y 0
frame $w.btnPnl -relief flat -borderwidth 1 -background $dialColor -width 300 -height 50
place $w.btnPnl -x 0 -y 130
label $w.msgPnl.message -text $message -background $dialColor -justify center -wraplength 270 -font dialogFont
pack $w.msgPnl.message -anchor center -pady 20 -padx 10 -expand 1 -fill both
if {$type == "ok"} {
button $w.btnPnl.okbut -text "OK" -background black -foreground white -relief flat -command {set _res "ok"} -width 8 -height 2 -highlightthickness 2 -font boldFont
grid $w.btnPnl.okbut -row 1 -column 1 -padx 125
} elseif {$type == "yesno"} {
button $w.btnPnl.yes -text "Yes" -background black -foreground white -relief flat -command {set _res "yes"} -width 8 -height 2 -highlightthickness 2 -font boldFont
button $w.btnPnl.no -text "No" -background black -foreground white -relief flat -command {set _res "no"} -width 8 -height 2 -highlightthickness 2 -font boldFont
grid $w.btnPnl.yes -row 1 -column 1 -padx 50
grid $w.btnPnl.no -row 1 -column 2
} else {
button $w.btnPnl.okbut -text "OK" -background $btnColor -relief flat -command {set _res "ok"} -width 8 -height 2
pack $w.btnPnl.okbut -side top -anchor center
}
if {$icon == "alert"} {
label $w.imgPnl.alertI -image .alert -compound top -background $dialColor
pack $w.imgPnl.alertI -fill both -expand 1 -pady 20
} elseif {$icon == "question"} {
label $w.imgPnl.quest -image .question -compound top -background $dialColor
pack $w.imgPnl.quest -fill both -expand 1 -pady 20
} else {
label $w.imgPnl.alertI -image .alert -compound top -background $dialColor
pack $w.imgPnl.alertI -fill both -expand 1 -pady 20
}
raise $w
vwait _res
destroy $w
return $::_res
}
}
I was trying something like this, but when i get invalid command name MsgDialog
set tid [thread::create {thread::wait}]
::thread::send -async $tid {MsgDialog .dialog "Are you ready for measurement ?" yesno question} answer
vwait answer
if {$answer == yes} {
#do something
}
Your dialog proc is fundamentally modal, since it returns a value. It therefore blocks until the user responds, because it can't return its value until the user gives it one.
To make it modeless, build it to just create itself and return. The buttons all then need to call procs (either global or with some fully qualified name) that will set the user value in some place you're waiting for it, then destroy the dialog properly.
This means that your "return" value must be global, the window ID variable must be global, the handler proc/procs must be global, and you'll need to trigger whatever processing you want the value for in some way that's too application specific for me to guess. It's a fair bit of work, but it's easier than trying to incorporate the threading library.
Have you tried simply removing the vwait? The difference between a modal dialog and a non-modal one is really nothing more than the modal dialog calling vwait and then doing a grab on the keyboard and mouse.

How do multiple amplitude fades work on ecasound?

I want to fade a track in and out at specific time codes. For example, I would like to take an audio file, and:
Start it at 100% Volume
Fade it to 20% at 2 seconds
Fade it to 100% at 4 seconds
Fade it to 20% at 6 seconds
Fade it to 100% at 8 seconds
Fade it to 20% at 10 seconds
Fade it to 100% at 12 seconds
Fade it to 0 at 14 seconds
I've been testing this with a constant tone generated by ecasound so that I can open the resulting file in Audacity and see the results visually. As far as I can tell, increasing the amplitude is relative, while decreasing it is not. It seems that if I fade the amplitude up, it affects the relative volume of the whole track and not just at the specific time I set the fade, which is where I'm getting lost.
Example commands
# generate the tone
ecasound -i tone,sine,880,20 -o:tone.wav
# Just the test to see that i can fade start it at 100 and fade it to 20.
ecasound -a:1 -i tone.wav -ea:100 -kl2:1,100,20,2,1 -a:all -o:test_1.mp3
# Fade it out and in
ecasound -a:1 -i tone.wav \
-ea:100 -kl2:1,100,20,2,1 \
-ea:100 -kl2:1,20,100,4,1 \
-a:all -o:test_2.mp3
# Fade it out and in with a peak of 500
ecasound -a:1 -i tone.wav \
-ea:100 -kl2:1,100,20,2,1 \
-ea:100 -kl2:1,20,500,4,1 \
-a:all -o:test_3.mp3
# Fade it out from 500, out, and then back to 500
ecasound -a:1 -i tone.wav \
-ea:100 -kl2:1,500,20,2,1 \
-ea:100 -kl2:1,20,500,4,1 \
-a:all -o:test_4.mp3
# Fade it out from 500, out to a low of 10, and then back to 500
ecasound -a:1 -i tone.wav \
-ea:100 -kl2:1,500,10,2,1 \
-ea:100 -kl2:1,10,500,4,1 \
-a:all -o:test_5.mp3
# Fade it out from 1000, out to a low of 10, and then back to 1000
ecasound -a:1 -i tone.wav \
-ea:100 -kl2:1,1000,10,2,1 \
-ea:100 -kl2:1,10,1000,4,1 \
-a:all -o:test_6.mp3
# The eventual result I'm looking for
ecasound -a:1 -i tone.wav \
-ea:100 -kl2:1,500,20,2,1 \
-ea:100 -kl2:1,20,500,4,1 \
-ea:100 -kl2:1,500,20,6,1 \
-ea:100 -kl2:1,20,500,8,1 \
-ea:100 -kl2:1,500,20,10,1 \
-ea:100 -kl2:1,20,500,12,1 \
-ea:100 -kl2:1,500,0,14,4 \
-a:all -o:test_7.mp3
The Results
The best I can tell from these results is that the amplitude of the whole track is relative to the difference between the low and the peak of all the fading effects. I'm not sure if this result is expected, but it's very confusing.
Also, in the last result (second to last in the image), the fades are no longer taking a full second each. In order to figure out why that may be, I took the final fade-to-zero off and the durations were back to normal. This does not seem like expected behavior.
# "Fixing" the fade durations
ecasound -a:1 -i tone.wav \
-ea:100 -kl2:1,500,20,2,1 \
-ea:100 -kl2:1,20,500,4,1 \
-ea:100 -kl2:1,500,20,6,1 \
-ea:100 -kl2:1,20,500,8,1 \
-ea:100 -kl2:1,500,20,10,1 \
-ea:100 -kl2:1,20,500,12,1 \
-a:all -o:test_8.mp3
As a side note, I've also tried changing the -ea values to the "current" amplitude with every line. It didn't make any difference (no matter what I set -ea to)
I have the very latest installed from git (2.8.1+dev). I had these same issues with 2.7.0, which is why I upgraded and eventually found myself here.
Am I doing this wrong?
-kl2
After a few hours of head scratching, I finally think I have it figured out. The "From" amplitude on every fade needs to be 100. If you are increasing the amplitude, the "To" amplitude is maximum / from * to.
So if you're trying to go from 20 to 100, it's 100 / 20 * 100 or 500. If you're trying to get to 120: 100 / 20 * 120 or 600. I assume this all makes perfect sense to someone, but I was perfectly stumped.
The working example (with a slightly higher bottom range in the middle to demonstrate):
ecasound -a:1 -i tone.wav \
-ea:100 -kl2:1,100,20,2,1 \
-ea:100 -kl2:1,100,500,4,1 \
-ea:100 -kl2:1,100,40,6,1 \
-ea:100 -kl2:1,100,250,8,1 \
-ea:100 -kl2:1,100,20,10,1 \
-ea:100 -kl2:1,100,500,12,1 \
-ea:100 -kl2:1,100,0,14,1 \
-a:all -o:test_7.mp3
And the output:
Keep in mind that these amplitudes are still relative. If you're going from 45% to 90%: 100 / 45 * 90 = 200, and then now if you drop to 20% of the current amplitude, it's actually 18% (.20 * 90), so going back to 100 would be 100 / 18 * 100 = 555.56
-klg
Just as I figured this out, and came here to post, I received a response from the ecasound mailing list. It's not a direct answer to the kl2 issue, but offers an alternative, easier-on-the-brain answer, which is the klg parameter.
-klg:fx-param,low-value,high-value,point_count,pos1,value1,...,posN,valueN
Generic linear envelope. This controller source can be used to map
custom envelopes to chain operator parameters. Number of envelope
points is specified in 'point_count'. Each envelope point consists of
a position and a matching value. Number of pairs must match
'point_count' (i.e. 'N==point_count'). The 'posX' parameters are given
as seconds (from start of the stream). The envelope points are
specified as float values in range '[0,1]'. Before envelope values are
mapped to operator parameters, they are mapped to the target range of
'[low-value,high-value]'. E.g. a value of '0' will set operator
parameter to 'low-value' and a value of '1' will set it to
'high-value'. For the initial segment '[0,pos1]', the envelope will
output value of 'value1' (e.g. 'low-value').
Here's the command to do what I need using klg instead of kl2:
ecasound -a:1 -i:tone.wav -ea:100 \
-klg:1,0,100,14,2,1,3,0.20,4,0.20,5,1,6,1,7,0.40,8,0.40,9,1,10,1,11,0.20,12,0.20,13,1,14,1,15,0 \
-o:test.mp3
The output is exactly the same as the 2nd track on the image.
This resulting command line is definitely a bit harder to read and hence debug, but may actually be easier to generate dynamically. Regardless, I now have 2 working options to resolve this problem.
And finally, here are my notes for how I figured out the coordinates of the klg command. The asterisks are the "points" which are listed in the klg parameter, the numbers at the top are seconds:
0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 2
1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
1.0 --* *-* *-* *-*
~ \ / \._./ \ / \
0.2 *-* *-* \
0.0 *----------
I hope this helps someone save at least the amount of hair that i've lost scratching my head.

Resources