<feColorMatrix> multiplies each pixel's RGBA by a 4 x 5 matrix or takes the shortcuts saturate, hueRotate and luminanceToAlpha. <feComponentTransfer> remaps each channel through <feFuncR/G/B/A> of type table, discrete, linear or gamma. <feConvolveMatrix> replaces each pixel with a weighted sum of its neighbors: sharpen, emboss and edge detection are just kernels. <feFlood>, <feImage>, <feTile> and <feComposite> (Porter-Duff over, in, out, atop, xor) are the plumbing.
<svg id="art" width="300" height="100" viewBox="0 0 570 190"
color-interpolation-filters="sRGB">
<filter id="duotone"><feColorMatrix type="saturate" values="0"/>
<feComponentTransfer><feFuncR type="table" tableValues=".15 .88"/>
<feFuncG type="table" tableValues=".2 .6"/><feFuncB type="table" tableValues=".22 .06"/>
</feComponentTransfer></filter>
<filter id="edges"><feConvolveMatrix kernelMatrix="-1 -1 -1 -1 8 -1 -1 -1 -1"/></filter>
</svg>
<script>
const colors = ['#1f5f8b', '#5b3f99', '#e09a10', '#3f7d3a', '#b5452f', '#2a9d8f'];
const spines = [42, 13, 56, 70, 34, 76].map((top, i) => `<path d="M120 150V${top}"
stroke="${colors[i]}" stroke-width="22" transform="rotate(${-25 + 10 * i} 120 240)"/>`);
art.insertAdjacentHTML('beforeend', `<defs><g id="mark">${spines.join('')}
<path d="M36 118Q120 142 204 118Q196 182 120 184Q44 182 36 118Z" fill="#263238"/>
</g></defs>`);
['none', 'url(#duotone)', 'url(#edges)']
.forEach((f, i) => art.insertAdjacentHTML('beforeend',
`<use href="#mark" x="${i * 190 - 20}" style="filter:${f}"/>`));
</script>
The duotone grays the mark, then maps black to ink and white to saffron with two-entry tables (discrete would posterize); the edge kernel sums to zero wherever color is flat.