% language=us runpath=texruns:manuals/luametafun

\environment luametafun-style

\startcomponent luametafun-bytemaps

\startchapter[title={Bytemaps}]

In this chapter we explore bytemaps, which essentially are arrays of bytes
that can be used to store states. There are two variants: single bytes and
triplets, or in \METAPOST\ speak: numerics or colors. The reason why we added
this to the engine is that we expect these to be used in situations where storing
states efficiently makes sense. The results can of course be bitmaps but also
become a path.

In the following example we create three bytemaps: one with a single row of
bytes, one with ten rows and columns black and white, and a same size bytemap
with three color components.

\startbuffer
\startMPcode
newbytemap 1 of  10;
newbytemap 2 of (10,10);
newbytemap 3 of (10,10,3);

for i=1 upto 3 :

    setbytemap i to 100 ;

    setbyte (0,0) of i to 150 ;
    setbyte (1,1) of i to 200 ;

    setbyte (8,0) of i to (150,150,0) ;
    setbyte (6,2) of i to (150,0,150) ;

    setbyte (3,3,1,2) of i to 150 ;
    setbyte (7,7,2,2) of i to (0,150,150) ;

    setbyte (2,7,1) of i to 0 ;
    setbyte (2,7,2) of i to 0 ;
    setbyte (2,7,0) of i to 255 ;

    fill
        unitsquare scaled 100
        shifted ((i-1)*110,0)
        withbytemap i ;

endfor ;
\stopMPcode
\stopbuffer

\typebuffer[option=TEX]

When the \type {setbyte} command gets two arguments, they indicate the
coordinates. When a third argument is passed, it specifies the (color) channel.
When four arguments are passes, the last two indicate the width and height of the
area to be set. When a color is assigned to a singly byte pane the color gets
reduced to a gray scale.

A bytemap is drawn using a path and \type {withbytemap} specifier. A \type {fill}
create a tight result, while a \type {draw} adds half the pen to the dimensions.

\startlinecorrection
\getbuffer
\stoplinecorrection

Before we show some more methods, we will do the same as above from the \LUA\
end.

\startbuffer
\startluacode
local random = math.random
local setbytemap = mp.setbytemap

function MP.MakeByteMap(i)
    mp.newbytemap(i,200,50,3)
    mp.fillbytemap(i,100,100,100)
    for j=1,5000 do
        setbytemap(i,
            random(0,199), random(0,49),
            random(0,255), random(0,255), random(0,255)
        )
    end
end
\stopluacode
\stopbuffer

\typebuffer[option=TEX]

\getbuffer

Next we use this \LUA\ function:

\startbuffer
\startMPcode
lua.MP.MakeByteMap(4);

fill
    unitsquare xyscaled (200,50)
    withbytemap 4 ;
\stopMPcode
\stopbuffer

\typebuffer[option=TEX]

\startlinecorrection
\getbuffer
\stoplinecorrection

Instead of a bitmap you can also get a path:

\startbuffer
\startluacode
local random = math.random
local setbytemap = mp.setbytemap

function MP.MakeBytePath(i)
    mp.newbytemap(i,100,100,1)
    mp.fillbytemap(i,100)
    for j=1,2500 do
        setbytemap(i,
            random(0,99),
            random(0,99),
            random(1,10)
        )
    end
end
\stopluacode
\stopbuffer

\typebuffer[option=TEX]

\getbuffer

This function will fill a gray scale bytemap. We can filter the values
and turn the result into a path:

\startbuffer
\startMPcode
lua.MP.MakeBytePath(5);

picture p[] ;

p[1] := image (
    for i=1 upto 10 :
        if bytefound i of 5 :
            drawdot (bytepath i of 5) shifted (.5,.5)
            withpen pencircle scaled (i/2) ;
        fi ;
    endfor ;
) ;

p[2] := image (
    for i=1 step 2 until 10 :
        if bytefound (i,i+1) of 5 :
            drawdot (bytepath (i,i+1) of 5) shifted (.5,.5)
            withpen pencircle scaled ((i+.5)/2) ;
        fi ;
    endfor ;
) ;

draw p[1]                 withcolor red    withtransparency (1,.5) ;
draw p[2] shifted (110,0) withcolor green  withtransparency (1,.5) ;
draw p[1] shifted (220,0) withcolor blue   withtransparency (1,.5) ;
draw p[2] shifted (220,0) withcolor yellow withtransparency (1,.5) ;
\stopMPcode
\stopbuffer

\typebuffer[option=TEX]

\startlinecorrection
\getbuffer
\stoplinecorrection

You can of course fill a bytemap with more meaningful data, as in:

\startbuffer
\startMPcode
newbytemap 1 of (100,102) ;
setbyte (0,0,100,102) of 1 to 150 ;

for i=0 upto 99 :
    setbyte(i,99*sin((i/99)*pi),2,4) of 1 to (2.55*i) ;
endfor ;

fill unitsquare xyscaled (400,100) withbytemap 1 ;
\stopMPcode
\stopbuffer

\typebuffer[option=TEX]

And just show the result as it is. Here we fill small areas:

\startlinecorrection
\getbuffer
\stoplinecorrection

These bytemaps are not implemented as datatype because it would not only
complicate matters but also be inconsistent with for instance equations and
scanning. The interface more looks like function calls. Currently we have these,
but more (variants) might show up:

\starttyping[option=MP]
newbytemap         index of (nx,ny) ;
newbytemap         index of (nx,ny,nz) ;
copybytemap        index to m ;
resetbytemap       index ;
resetbytemaps      ;
setbytemap         index to value;
setbytemap         index to (r,g,b);

setbytemapoption   of index to n ;
setbytemapoffset   of index to (x,y) ;
reducebytemap      of index ;
setbyte            (x,y) of index to value ;
setbyte            (x,y) of index to (r,g,b) ;
setbyte            (x,y,z) of index to value ;
setbyte            (x,y,z) of index to (r,g,b) ;
setbyte            (x,y,dx,dy) of index to value ;
setbyte            (x,y,dx,dy) of index to (r,g,b) ;

withbytemap        index
withbytemask       value
bytevalue          (x,y) of index
bytevalue          (x,y,[z]) of index
bytefound          value of index
bytefound          (min,max) of index
bytepath           value of index
bytepath           (min,max) of index
\stoptyping

The \type {withbytemask} directive is not really a primitive but a backend option
instead.

Currently the following \LUA\ functions are available:

\starttyping[option=LUA]
mp.newbytemap     (nx,ny,nz)
mp.setbytemap     (x,y,value)
mp.setbytemap     (x,y,r,g,b)
mp.getbytemap     (x,y)
mp.getbytemap     (x,y,z)
mp.fillbytemap    (x,y,value)
mp.fillbytemap    (x,y,r,g,b)

mp.getbytemapdata ()     : return nx, ny, nz
mp.getbytemapdata (true) : return nx, ny, nz, data
\stoptyping

These use the \type {mplib} library functions that expect an instance as argument
but in the \CONTEXT\ \type {mp} library that is taken care of automatically.

You can stack a bitmap result on top of another object without it covering that
object by using the \type {withbytemask} directive.

\startbuffer
\startMPcode
newbytemap 2 of (10,10,3);

setbytemap 2 to (100,0,0) ;

fill unitcircle scaled 100                 withcolor blue ;
fill unitsquare scaled 100 shifted (110,0) withbytemap 2 ;
fill unitcircle scaled 100 shifted (220,0) withcolor blue ;
fill unitsquare scaled 100 shifted (220,0) withbytemap 2 withbytemask 100 ;
\stopMPcode
\stopbuffer

\typebuffer[option=TEX]

\startlinecorrection
\getbuffer
\stoplinecorrection

You can clip a bytemap to what actually is used.

\startbuffer
\startMPcode
newbytemap 4 of (4,4) ;
setbytemap 4 to 255 ;

setbyte (1,1) of 4 to 100 ;
setbyte (1,2) of 4 to 200 ;
setbyte (2,1) of 4 to 200 ;
setbyte (2,2) of 4 to 100 ;

pickup pencircle scaled 2;

fill unitsquare scaled 100 withbytemap 4 ;
draw unitsquare scaled 100 withcolor "darkred" ;

path b ; b := bytemapbounds 255 of 4;

message (llcorner b); % metapost > message 1 1
message (urcorner b); % metapost > message 2 2

copybytemap 4 to 5 ;
clipbytemap 5 to 255 ;

fill unitsquare scaled 100 shifted (110,0) withbytemap 5 ;
draw unitsquare scaled 100 shifted (110,0) withcolor "darkblue" ;
\stopMPcode
\stopbuffer

\typebuffer[option=TEX]

Here we copy the bytemap because we want to show the original and when we draw
(fill) the bytemap we only add a reference so the bitmap that gets embedded is
the last one assigned to that slot!

\startlinecorrection
\getbuffer
\stoplinecorrection

In \LUAMETAFUN\ you can use \POTRACE\ to turn a bitmap into an outline and
bytemaps can be a source too:

\startbuffer
\startMPcode
newbytemap 1 of (10,10) ;
setbytemap 1 to 100 ;

for i=2 upto 8 :
    for j=2 upto 8 :
        setbyte (i,j) of 1 to 200 ; % 49 "1"
    endfor ;
endfor ;
for i=3 upto 7 :
    for j=3 upto 7 :
        setbyte (i,j) of 1 to 150;
    endfor ;
endfor ;

picture p[] ;

p[1] := image (
    fill unitsquare ysized 50 withbytemap 1 ;
);

p[2] := image (
    draw lmt_potraced [
        explode = true,
        value   = (char 200), % "1",
        bytemap = 1
    ] ysized 50
    withcolor "darkblue"
    withpen pencircle scaled 1 ;
);

p[3] := image (
    draw lmt_potraced [
        explode = true,
        value   = (char 150),
        bytemap = 1
    ] ysized 50
    withcolor "darkred"
    withpen pencircle scaled 1 ;
);

for i=1 upto 3 :
    draw p[i]
        shifted - center p[i]
        shifted ((i-1) * 60,0)
    ;
endfor ;
\stopMPcode
\stopbuffer

\typebuffer[option=TEX]

You need to scald these images to your needs; here we just normalize them to the
same size. Of course you might need to fine tune the tracing.

\startlinecorrection
\getbuffer
\stoplinecorrection

You can also load a bitmap from file, for instance:

\startbuffer
\startMPcode
triplet bm ; bm := loadbytemapfromfile(3, "mill.png") ;
numeric nx ; nx := redpart bm;
numeric ny ; ny := greenpart bm;

newinternal v ;
for col=nx/3 upto 2nx/3 :
    for row=ny/3 upto 2ny/3 :
        v := bytevalue (col,row) of 3 ;
        if (v > 50) and (v < 150) :
            setbyte (col,row) of 3 to 255 ;
        fi
    endfor ;
endfor ;

draw image (
    fill unitsquare xyscaled (nx,ny)
        withbytemap 3 ;
    draw unitsquare xyscaled (nx/3,ny/3)
        shifted (nx/3,ny/3)
        withpen pencircle scaled 5
        withcolor "darkred" ;
) ysized 8cm ;
\stopMPcode
\stopbuffer

\typebuffer[option=TEX]

Of course manipulating bitmaps will add to the the runtime but in this case it
can be neglected (see \in {figure} [fig:bytemap-mill]).

\startplacefigure[reference=fig:bytemap-mill,title={A bytemap loaded from file.}]
    \getbuffer
\stopplacefigure

Here are a few more examples. We fill a few areas first. Of course this can be
done with regular paths but imagine more complex patterns. We make a copy of that
bytemap and then convert the pixels to gray scales:

\startbuffer
\startMPcode
newbytemap 4 of (50,50,3) ;

setbyte (1,1,47,47) of 4 to (0,200,0) ;
setbyte (5,5,42,42) of 4 to (0,0,200) ;
setbyte (9,9,34,34) of 4 to (200,0,0) ;

fill unitsquare
  scaled 50
  withbytemap 4 ;

copybytemap 4 to 8;

reducebytemap 8;

fill unitsquare
  scaled 50 shifted (55,0)
  withbytemap 8 ;
\stopMPcode
\stopbuffer

\typebuffer[option=TEX]

\startlinecorrection
\getbuffer
\stoplinecorrection

This example shows how to use an offset (which can save some calculations) as
well as the fact that we can rotate a bitmap (see \in {figure}
[fig:bytemap-rotate]).

\startbuffer
\startMPcode
newbytemap 3 of (100,100,3) ;
setbytemap 3 to 50 ;
setbytemapoffset (1,1) of 3;
numeric n ;
for i=1 step 5 until 100 :
    n := 100 ;
    for j=1 step 5 until 100 :
        setbyte (i,j) of 3 to n ;
        n := n + 5 ;
    endfor ;
endfor ;

draw unitsquare
  scaled 200
  rotated 45
  withbytemap 3 ;
\stopMPcode
\stopbuffer

\typebuffer[option=TEX]

\startplacefigure[reference=fig:bytemap-rotate,title={Using offset.}]
    \getbuffer
\stopplacefigure

We store bytes which normally represent a small integer value, or in our case an
unsigned cardinal (in \MODULA\ speak). However, we can also use that byte for storing
a small float, in our case a posit. The number of possible value sis of course
limited, as can be seen in \in {table} [bytemaps:posit].

\startbuffer[mybuffer]
\startluacode
 -- local s = posit.tochar(1.23)
 -- local n = posit.fromchar(s)
    context.startsimplecolumns { n = 6 }
        context.starttabulate { "|rTj0|lT|" }
            for i=0,255 do
                local n = posit.frombyte(i)
                context.NC() context(n)
                context.NC() context.formatted.darkred("%02X",i)
                context.NR()
            end
        context.stoptabulate()
    context.stopsimplecolumns()
\stopluacode
\stopbuffer

\startplacetable[title={Possible float values in a bytemap.},reference=bytemaps:posit]
    \scale[height=.8th]{\getbuffer[mybuffer]}
\stopplacetable

In order to get this working, we need to set option ~2, as in the following
example. \footnote {Options are a bitset. Option~1 makes a bytemap persistent
across figures, and is handy when you want to access it later on.} Because we use
small values, we get a rather dark result but we can expand the bitmap.

\startbuffer
\startMPcode
    newbytemap 1 of (10,10) ;
    setbytemapoptions 1 to 2 ;
  % setbytemap 1 to 255 ;
  % setbytemap 1 to 1.5 ;
    for i=0 upto 9 :
        for j=0 upto 9 :
            n := uniformdeviate(1) ;
            setbyte (i,j) of 1 to n ;
          % show((n,bytevalue (i,j) of 1)) ;
        endfor ;
    endfor ;

    draw image (
        draw unitsquare                 withbytemap 1 ;
        draw unitsquare shifted (1.1,0) withbytemap 1 withbyteexpansion 127 ;
        draw unitsquare shifted (2.2,0) withbytemap 1 withbyteexpansion 255 ;
    ) scaled 100;
\stopMPcode
\stopbuffer

\typebuffer[option=TEX]

This expansion option is mostly there because of tracing (and showing off in the
manual) but we can imagine users finding some application.

\startlinecorrection
    \getbuffer
\stoplinecorrection

\startbuffer
\startMPcode
    newbytemap 1 of (20,5) ;
    setbytemapoptions 1 to 2 ;
    setbytemap 1 to 0 ;
    for i=0 upto 19 :
        for j=0 upto 4 :
            n := 0 randomized 10 ;
            setbyte (i,j) of 1 to n ;
          % show((n,bytevalue (i,j) of 1)) ;
        endfor ;
    endfor ;

    pickup pencircle scaled 2 ;
    drawdot (bytepath (1,2) of 1) scaled 10 withcolor "darkred" ;
    drawdot (bytepath (2,3) of 1) scaled 10 withcolor "darkgreen" ;
    drawdot (bytepath (3,4) of 1) scaled 10 withcolor "darkblue" ;

    for i=0 upto 19 :
        for j=0 upto 4 :
            n := bytevalue (i,j) of 1;
            draw
                textext (decimal n)
                scaled .2
                shifted (10i,10j-3)
            ;
        endfor ;
    endfor ;
\stopMPcode
\stopbuffer

The next code produces \in {figure} [bytemaps:moreposit] which demonstrates that
we get back a float instead of an integer in some cases. Posits (for now) only
make sense for numeric bytemaps.

\typebuffer[option=TEX]

A nice aspect of posits is that a comparison is very fast because we can directly
compare the integer representation. Another advantage over for instance so called
minifloats is that they waste less on \type {NaN} and \type {Infinity} as well as
are most accurate in the smaller values.

\startplacefigure[title={Some operations return posits.},reference=bytemaps:moreposit]
    \scale[width=1tw]{\getbuffer}
\stopplacefigure

Filing a bytemap with values can be done with a \METAPOST\ loop or at
the \LUA\ end. A rather convenient variant is using a function and delate the
loop to the engine. This is more efficient when we also need access to the
currently set values. Here are some examples:

\startbuffer
\startluacode
    local round  = math.round
    local random = math.random

    function MP.bw_inverse(x,y,s)
        return 255 - s
    end
    function MP.bw_darker(x,y,s)
        return 0.8 * s
    end
    function MP.bw_grain(x,y,s)
        return s - 25 + random(50)
    end
\stopluacode
\stopbuffer

\getbuffer \typebuffer[option=TEX]

\startbuffer
\startMPcode
    triplet bm ; bm := loadbytemapfromfile(1, "mill.png") ;
    copybytemap 1 to 2;
    copybytemap 1 to 3;
    copybytemap 1 to 4;
    draw lmt_bytemap [
        bytemap       = 1,
    ] bytemapscaled 1 xsized .25TextWidth ;
    draw lmt_bytemap [
        bytemap       = 2,
        colorfunction = "bw_inverse",
    ] bytemapscaled 1 xsized .25TextWidth xshifted .25TextWidth;
    draw lmt_bytemap [
        bytemap       = 3,
        colorfunction = "bw_darker",
    ] bytemapscaled 1 xsized .25TextWidth xshifted .50TextWidth;
    draw lmt_bytemap [
        bytemap       = 4,
        colorfunction = "bw_grain",
    ] bytemapscaled 1 xsized .25TextWidth xshifted .75TextWidth;
\stopMPcode
\stopbuffer

\typebuffer[option=TEX]

The replacement happens in-place which is why we make some copied to
work with.

\startlinecorrection
\getbuffer
\stoplinecorrection

Here we manipulated the existing bytes. In the next example we don't do that
but nevertheless use the function approach:

\startbuffer
\startluacode
    function MP.bc_test(x,y,b1,b2,b3)
        return x/2, y/2, (x+y)/500
    end
\stopluacode
\stopbuffer

\getbuffer \typebuffer[option=TEX]

Here we demonstrate the downsample feature. Doing that in pure \LUA\ is actually
not that slow but of course this one is faster.

\startbuffer
\startMPcode
    newbytemap 1 of (1000,1000,3) ;
    newbytemap 2 of (1,1,1);
    picture p ; p := image ( draw lmt_bytemap [
        bytemap       = 1,
        colorfunction = "bc_test",
    ] ; ) ;
    downsamplebytemap(1,2,40);
    copybytemap 2 to 3 ;
    reducebytemap 3;
    draw unitsquare bytemapscaled 1 xsized (TextWidth/3) withbytemap 1 ;
    draw unitsquare bytemapscaled 1 xsized (TextWidth/3) shifted ( TextWidth/3,0) withbytemap 2 ;
    draw unitsquare bytemapscaled 1 xsized (TextWidth/3) shifted (2TextWidth/3,0) withbytemap 3 ;
\stopMPcode
\stopbuffer

\typebuffer[option=TEX]

\startlinecorrection
\getbuffer
\stoplinecorrection

Here we apply downsampling to a \quote {real} image:

\startbuffer
\startMPcode
    triplet bm ; bm := loadbytemapfromfile(1, "mill.png") ;
    newbytemap 2 of (1,1,1);
    downsamplebytemap(1,2,2);
    draw unitsquare
        bytemapscaled 1 xsized .5 TextWidth
        withbytemap 1
    ;
    draw unitsquare
        bytemapscaled 1 xsized .5TextWidth
        shifted (.5TextWidth,0)
        withbytemap 2
    ;
\stopMPcode
\stopbuffer

\typebuffer[option=TEX]

Sampling takes the average of the given cell width, in this case 2. The result in
normally quite okay.

\startlinecorrection
\getbuffer
\stoplinecorrection

Let's summarize some of these manipulations. Bytemaps are mostly there for
special purposes and not so much for manipulating images that one normally would
include with \typ {\externalfigure}. One can fill a bytemap manually, from a
lossless \PNG\ file or a lossy \JPG\ file. For runtime usage the bytemaps loaded
from file can best not be too large. When the resolution is reasonable
performance is quite ok. And if you're worried about a resolution, just think of
the days that a few megapixel images were quite acceptable.

There are various ways to make a large bytemap a bit smaller. For instance, you
can reduce the color range in the output file. Here we can either clip or round
and in both cases an 8 bit color component becomes a 4 bit one. So, for every six
bytes (two pixels) we get three bytes back. In practice, because we now have less
distinctive values, compression also works out better.

%     loadbytemap(1,"P1010013.jpg") ;

\startbuffer
\startMPcode
    newbytemap 1 of (1,1,1) ;
    newbytemap 2 of (1,1,1) ;
    newbytemap 3 of (1,1,1) ;
    loadbytemap (1,"hacker.jpg") ;
    copybytemap 1 to 2 ;
    copybytemap 1 to 3 ;
    draw bytemap 1 xsized 3cm xshifted 0.0cm ;
    draw bytemap 2 xsized 3cm xshifted 3.1cm withreduction "round" ;
    draw bytemap 3 xsized 3cm xshifted 6.2cm withreduction "clip" ;
\stopMPcode
\stopbuffer

\typebuffer[option=TEX]

Reduction gives us:

\startlinecorrection
\getbuffer
\stoplinecorrection

Another way to reduce the size is downgrading. Here we also reudce the number
of distinct values, and you can go pretty extreme here:

\startbuffer
\startMPcode
    newbytemap 1 of (1,1,1) ;
    newbytemap 2 of (1,1,1) ;
    newbytemap 3 of (1,1,1) ;
    loadbytemap (1,"hacker.jpg") ;
    downgradebytemap (1,2,20) ;
    downgradebytemap (1,3,40) ;
    draw bytemap 1 xsized 3cm xshifted 0.0cm ;
    draw bytemap 2 xsized 3cm xshifted 3.1cm ;
    draw bytemap 3 xsized 3cm xshifted 6.2cm ;
\stopMPcode
\stopbuffer

\typebuffer[option=TEX]

\startlinecorrection
\getbuffer
\stoplinecorrection

A probably nicer result is possible with downsampling:

%  loadbytemap(1,"P1010013.jpg") ;

\startbuffer
\startMPcode
    newbytemap 1 of (1,1,1) ;
    newbytemap 2 of (1,1,1) ;
    newbytemap 3 of (1,1,1) ;
    loadbytemap (1,"hacker.jpg") ;
    downsamplebytemap (1,2,2) ;
    downsamplebytemap (1,3,4) ;
    draw bytemap 1 xsized 3cm xshifted 0.0cm ;
    draw bytemap 2 xsized 3cm xshifted 3.1cm ;
    draw bytemap 3 xsized 3cm xshifted 6.2cm ;
\stopMPcode
\stopbuffer

\typebuffer[option=TEX]

\startlinecorrection
\getbuffer
\stoplinecorrection

In the next example we show a combination of effects: downsampling
and using a palette:

\startbuffer
\startluacode
    local sin = math.sin
    local cos = math.cos
    local p = { }
    for i=0,255 do
        p[i] = { 255*sin(i), 255*cos(i), i }
    end
    figures.palettes.foo = p
\stopluacode

\startMPcode
    newbytemap 1 of (1,1,1) ;
    newbytemap 2 of (1,1,1) ;
    loadbytemap (1,"mill.png") ;
    downsamplebytemap (1,2,4) ;
    draw bytemap 1 xsized 7cm xshifted 0.0cm withpalette "foo" ;
    draw bytemap 2 xsized 7cm xshifted 7.2cm withpalette "foo" ;
\stopMPcode
\stopbuffer

\typebuffer[option=TEX]

\startlinecorrection
\getbuffer
\stoplinecorrection

We have dedicated chapters to drawing functions but sometimes it makes sense
to combine technologies. Take the following \METAPOST\ code: \footnote {This is an example
from Mikaels math book that we use for testing and benchmarks.}

\starttyping
\startMPpage
draw image (
    newinternal old, new;
    newinternal oldx, oldy;
    for r=2.75 step 0.001 until 4 :
        old := 0.2 ;
        oldx := 0;
        oldy := 0;
        for i=0 upto 800 :
            new := r*old*(1 - old);
            if i > 699 :
                if (abs(r-oldx) > 0.001) or (abs(old-oldy) > 0.001) :
                    draw (r,old);
                    oldx := r;
                    oldy := old;
                fi
            fi
            old := new ;
        endfor
    endfor ;
) xsized 5.75cm withpen pencircle scaled .4 ;
\stopMPpage
\stoptyping

This is an optimized version but even than on my current (2018) laptop it takes
2.170 seconds to get done. We can of course cache the result but then we still
need to include a 500 MB \PDF\ image, which also takes time. But we can do better!

\startbuffer
\startluacode
local v0 <const> =   0
local v1 <const> = 200 --  63
local v2 <const> = 225 -- 127

function MP.bifurkation(resolution,usemin)
    local bmp = bytemap.newdomain(2.75,4,0,1,resolution)
    local set = usemin and bmp.min or bmp.set
    for r = 2.75, 4, 0.002 do
        local old = 0.2
        for i = 0, 800 do
            local new = r * old * (1 - old)
            if i >  699 then
                set(r,old,v0,v1,v2)
            end
            old = new
        end
    end
 -- bmp.clip()
    mp.newbytemap(1,bmp.bytemap)
end
\stopluacode
\stopbuffer

\typebuffer[option=TEX] \getbuffer

In \in {figures} [bifurkation-set] and \in [bifurkation-min] we see a few
renderings. These eight images took 0.199 seconds. A single 1000 cell instance
takes 0.035 seconds:

\starttyping[option=TEX]
\startMPpage
    lua.MP.bifurkation(1000,true) ;
    draw bytemap 1 xsized 5.75cm ;
\stopMPpage
\stoptyping

\startplacefigure[reference=bifurkation-set,title=Bifurkation using \type {set} setter.]
    \startcombination[nx=2,ny=2,style=\tttf]
      {\startMPcode lua.MP.bifurkation( 250,false); draw bytemap 1 ysized .2TextWidth ; \stopMPcode} {250}
      {\startMPcode lua.MP.bifurkation( 500,false); draw bytemap 1 ysized .2TextWidth ; \stopMPcode} {500}
      {\startMPcode lua.MP.bifurkation( 750,false); draw bytemap 1 ysized .2TextWidth ; \stopMPcode} {750}
      {\startMPcode lua.MP.bifurkation(1000,false); draw bytemap 1 ysized .2TextWidth ; \stopMPcode} {1000}
    \stopcombination
\stopplacefigure

\startplacefigure[reference=bifurkation-min,title=Bifurkation using \type {min} setter.]
    \startcombination[nx=2,ny=2,style=\tttf]
      {\startMPcode lua.MP.bifurkation( 250,true); draw bytemap 1 ysized .2TextWidth ; \stopMPcode} {250}
      {\startMPcode lua.MP.bifurkation( 500,true); draw bytemap 1 ysized .2TextWidth ; \stopMPcode} {500}
      {\startMPcode lua.MP.bifurkation( 750,true); draw bytemap 1 ysized .2TextWidth ; \stopMPcode} {750}
      {\startMPcode lua.MP.bifurkation(1000,true); draw bytemap 1 ysized .2TextWidth ; \stopMPcode} {1000}
    \stopcombination
\stopplacefigure

The \type {newdomain} function call returns a table with the following fields:

\starttabulate[|l|l|]
\NC \type {bytemap}           \NC the bytemap user data object \NC \NR
\NC \type {sx}                \NC the horizontal multiplier \NC \NR
\NC \type {sy}                \NC the vertical multiplier \NC \NR
\NC \type {xy(x,y)}           \NC the scaler (returns x and y) \NC \NR
\NC \type {get(x,y)}          \NC return the current value (byte) \NC \NR
\NC \type {set(x,y,v)}        \NC sets a point to a value (one byte) \NC \NR
\NC \type {min(x,y,v0,v1,v3)} \NC sets a point to a value (9 bytes) \NC \NR
\NC \type {add(x,y,v0,v1,v3)} \NC adds values (first time it's a set) \NC \NR
\NC \type {clip()}            \NC clips the result to used bytes (not 255) \NC \NR
\stoptabulate

The following method also works; we might extend it on demand:

\startbuffer
\startluacode
local v0 <const> =   0
local v1 <const> = 200 --  63
local v2 <const> = 225 -- 127

function MP.bifurkation(bmp) -- or document.bifurkation
    local set = bmp.min
    for r = bmp.xmin, bmp.xmax, 0.002 do
        local old = 0.2
        for i = 0, 800 do
            local new = r * old * (1 - old)
            if i >  699 then
                set(r,old,v0,v1,v2)
            end
            old = new
        end
    end
end
\stopluacode

\startMPcode
    draw lmt_domainmap [
        xmin           = 2.75,
        xmax           = 4.00,
        ymin           = 0,
        ymax           = 1,
        domainfunction = "bifurkation",
    ] ysized .40TextWidth;
\stopMPcode
\stopbuffer

\typebuffer[option=TEX]

% \getbuffer % no need to show

\stopchapter

\stopcomponent

