Newer
Older
casic-smartcity-well-front / static / Cesium / ThirdParty / bitmap-sdf.js
[wangxitong] on 8 Jul 2021 4 KB mars3d总览
/*
* https://github.com/dy/bitmap-sdf
* Calculate SDF for image/bitmap/bw data
* This project is a fork of MapBox's TinySDF that works directly on an input Canvas instead of generating the glyphs themselves.
*/

    var INF = 1e20;

    function clamp(value, min, max) {
        return min < max
            ? (value < min ? min : value > max ? max : value)
            : (value < max ? max : value > min ? min : value)
    }

    function calcSDF(src, options) {
        if (!options) options = {}

        var cutoff = options.cutoff == null ? 0.25 : options.cutoff
        var radius = options.radius == null ? 8 : options.radius
        var channel = options.channel || 0
        var w, h, size, data, intData, stride, ctx, canvas, imgData, i, l

        // handle image container
        if (ArrayBuffer.isView(src) || Array.isArray(src)) {
            if (!options.width || !options.height) throw Error('For raw data width and height should be provided by options')
            w = options.width, h = options.height
            data = src

            if (!options.stride) stride = Math.floor(src.length / w / h)
            else stride = options.stride
        }
        else {
            if (window.HTMLCanvasElement && src instanceof window.HTMLCanvasElement) {
                canvas = src
                ctx = canvas.getContext('2d')
                w = canvas.width, h = canvas.height
                imgData = ctx.getImageData(0, 0, w, h)
                data = imgData.data
                stride = 4
            }
            else if (window.CanvasRenderingContext2D && src instanceof window.CanvasRenderingContext2D) {
                canvas = src.canvas
                ctx = src
                w = canvas.width, h = canvas.height
                imgData = ctx.getImageData(0, 0, w, h)
                data = imgData.data
                stride = 4
            }
            else if (window.ImageData && src instanceof window.ImageData) {
                imgData = src
                w = src.width, h = src.height
                data = imgData.data
                stride = 4
            }
        }

        size = Math.max(w, h)

        //convert int data to floats
        if ((window.Uint8ClampedArray && data instanceof window.Uint8ClampedArray) || (window.Uint8Array && data instanceof window.Uint8Array)) {
            intData = data
            data = Array(w * h)

            for (i = 0, l = intData.length; i < l; i++) {
                data[i] = intData[i * stride + channel] / 255
            }
        }
        else {
            if (stride !== 1) throw Error('Raw data can have only 1 value per pixel')
        }

        // temporary arrays for the distance transform
        var gridOuter = Array(w * h)
        var gridInner = Array(w * h)
        var f = Array(size)
        var d = Array(size)
        var z = Array(size + 1)
        var v = Array(size)

        for (i = 0, l = w * h; i < l; i++) {
            var a = data[i]
            gridOuter[i] = a === 1 ? 0 : a === 0 ? INF : Math.pow(Math.max(0, 0.5 - a), 2)
            gridInner[i] = a === 1 ? INF : a === 0 ? 0 : Math.pow(Math.max(0, a - 0.5), 2)
        }

        edt(gridOuter, w, h, f, d, v, z)
        edt(gridInner, w, h, f, d, v, z)

        var dist = window.Float32Array ? new Float32Array(w * h) : new Array(w * h)

        for (i = 0, l = w * h; i < l; i++) {
            dist[i] = clamp(1 - ((gridOuter[i] - gridInner[i]) / radius + cutoff), 0, 1)
        }

        return dist
    }

    // 2D Euclidean distance transform by Felzenszwalb & Huttenlocher https://cs.brown.edu/~pff/dt/
    function edt(data, width, height, f, d, v, z) {
        for (var x = 0; x < width; x++) {
            for (var y = 0; y < height; y++) {
                f[y] = data[y * width + x]
            }
            edt1d(f, d, v, z, height)
            for (y = 0; y < height; y++) {
                data[y * width + x] = d[y]
            }
        }
        for (y = 0; y < height; y++) {
            for (x = 0; x < width; x++) {
                f[x] = data[y * width + x]
            }
            edt1d(f, d, v, z, width)
            for (x = 0; x < width; x++) {
                data[y * width + x] = Math.sqrt(d[x])
            }
        }
    }

    // 1D squared distance transform
    function edt1d(f, d, v, z, n) {
        v[0] = 0;
        z[0] = -INF
        z[1] = +INF

        for (var q = 1, k = 0; q < n; q++) {
            var s = ((f[q] + q * q) - (f[v[k]] + v[k] * v[k])) / (2 * q - 2 * v[k])
            while (s <= z[k]) {
                k--
                s = ((f[q] + q * q) - (f[v[k]] + v[k] * v[k])) / (2 * q - 2 * v[k])
            }
            k++
            v[k] = q
            z[k] = s
            z[k + 1] = +INF
        }

        for (q = 0, k = 0; q < n; q++) {
            while (z[k + 1] < q) k++
            d[q] = (q - v[k]) * (q - v[k]) + f[v[k]]
        }
    }

    export default calcSDF;