JavaScript 将 Canvas 转换为 PGM

JavaScript Convert Canvas To PGM

我正在尝试将 JavaScript 中的 canvas 转换为 pgm。 在我转换文件并将其下载到我的桌​​面后,它具有 pgm 扩展名,但是当我用编辑器打开它时,它被编码为 png。 我认为如果 JavaScript 无法识别您要转换为的扩展名,它会默认将其转换为 png。 所以 JavaScript 可以转换为 png、jpeg 等,但不能转换为 pgm ?

我的问题是可以使用 JavaScript 将 canvas 转换为 pgm,如果不能,是否有任何可用的插件或 Js 库可以这样做。

function image_download() {
    canvas = $('#canvas').get(0);
    var image = new Image();
    image.src = canvas.toDataURL("image/pgm");


    var download = document.createElement('a');
    download.href = image.src;
    download.download = 'map.pgm';
    download.click();
}

PGM 编剧

为了将 canvas 保存为 PGM,我刚刚转到 http://netpbm.sourceforge.net/doc/pgm.html,其中详细说明了文件格式,并使用该信息创建了一个将 canvas 转换为 PGM bin 的函数。

转化

该文件有一个描述文件和内容的 header,然后是一个 8 位或 16 位值的二进制数组

所以从 header 开始并将其创建为字符串

const CR = "\n";
var header = "P5" // required
header += CR; // add whitespace char
header += canvas.width;     
header += CR; // add whitespace char
header += canvas.height;     
header += CR; // add whitespace char

header 未完成,但我们需要获取像素值的最大值,因此将转换图像并找到最大值。

因此通过 2D 上下文获取像素数据。

var imgData = ctx.getImageData(0,0,ctx.canvas.width,ctx.height);

关于您想要的数据,您有多种选择。该格式为某些值指定对数标度,为其他值指定线性标度。如果保持伽玛很重要,我会留给你找到转换。 (注意只有当你计划广播或保存光值时才需要,例如天文图像)

有一个实用程序可以将线性伽玛函数转换为 BT.709 伽玛函数。请参阅链接文档

我添加了一些简单的格式。两个是16位,最大值为3*255.

如果您从 jpeg 编码图像转换,最好通过首先将像素 RGB 转换为 HSL 并仅将 L 保存为 8 位值来从图像中提取亮度,这将阻止 jpeg 质量较差的色调和饱和度影响结果的数据(jpeg 具有接近无损的灰度数据)

const formats = {
    mean : {
        convert (r, g, b) { bin[j++] = ((r + g + b) / 3 ) | 0 },
        dataStore (w, h) { return new Uint8Array(w * h) },
    },
    summed : {
        convert (r, g, b) { bin[j++] = r + g + b },
        dataStore (w, h) { return new Uint16Array(w * h) },
    },
    meanLog : {
        convert (r, g, b) { bin[j++]  = Math.sqrt((r * r + g * g + b * b)/ 3) | 0 },
        dataStore (w, h) { return new Uint8Array(w * h) },
    },
    summedLog : {
        convert (r, g, b) { bin[j++] = Math.sqrt(r * r + g * g + b * b) | 0 },
        dataStore (w, h) { return new Uint16Array(w * h) },
    }
}
var imgData = ctx.getImageData(0,0,ctx.canvas.width,ctx.canvas.height);
var format = formats.mean;
var bin = format.dataStore(canvas.width, canvas.height);
var r,g,b,summed,mean,summedLog,meanLog;
var i = 0;
var j = 0;
var max = 0;
var d = imgData.data; // shorthand var to pixel data
while(i < d.length){
    format.convert(d[i++],d[i++],d[i++]);
    max = Math.max(bin[j-1],max);
    i++; // skip alpha
}
header += max;     // max value. 
header += CR; // add whitespace char
// We have all that is needed to create the file so first convert
// header string to ascII
var fileBin = new Uint8Array(header.length + bin.length);
for(i = 0; i < header.length; i++){
     fileBin[i] = header.charCodeAt(i) & 0b01111111; // strip of top bit just in case superstitious coder reads this
}
fileBin.set(new Uint8Array(bin.buffer),header.length); // add the pixel data

// fileBin is the 8bit bin of the PGM file
// Note transparent pixels are black.
// Note I do not check for all black. Format doc indicates that a max val in the head of zero is illegal. What that means you will have to find out.

就是这样。您可以将其转换为 Base64 编码,或者直接保存

请注意,在此答案之前我从未使用过或听说过这种图像格式,上面的代码是对答案顶部链接的文档的快速解释。如果它有效,我不知道,因为我没有任何东西可以测试它。代码没有语法和运行时错误。

它更像是一个指南,展示了如何为特定格式创建二进制文件

更新

JPEG 亮度保留

我想我还会添加一种格式来处理 JPEG 图像,它只需要一个良好的 RGB 到 HSL 函数。

   // NOTE to prevent data loss the out put of lum has been
   // scaled from the normal 0-100 to 0-255 for the example PGM image format
   // If you use this function to get HSL values compatible with HSL CSS 
   // colours change the multipliers from 255 to 100. Commented with [*1]
   function rgb2hsl(r,g,b){ // integers in the range 0-255
        var minC, maxC, dif, h, l, s;
        h = l = s = 0;
        r /= 255;  // normalize channels
        g /= 255;
        b /= 255;
        min = Math.min(r, g, b);
        max = Math.max(r, g, b);
        if(min === max){  // [*1] no colour so early exit
            return {
                h, s,
                l : Math.floor(min * 255), // this normally is 0-100
            }
        }
        dif = max - min;
        l = (max + min) / 2;
        if (l > 0.5) { s = dif / (2 - max - min) }
        else { s = dif / (max + min) }
        if (max === r) {
            if (g < b) { h = (g - b) / dif + 6.0 }
            else { h = (g - b) / dif }                   
        } else if(max === g) { h = (b - r) / dif + 2.0 }
        else {h = (r - g) / dif + 4.0 }   
        h = Math.floor(h * 60);
        s = Math.floor(s * 100);
        l = Math.floor(l * 255); // [*1] this normally is 0-100
        return {h, s, l};
    },

然后将以下内容添加到格式 object

    formats.jpegLumPreserve = {
        convert (r, g, b) { bin[j++] = rgb2hsl(r + g + b).l},
        dataStore (w, h) { return new Uint8Array(w * h) },
    }

如果您从最初存储为 jpeg (jpg) 的图像中保存,您应该使用这种格式

    format = formats.jpegLumPreserve;

感性

因为总是有一些聪明人啊...需要兜售感知亮度转换的人我也会补充一点,因为他们通常会弄错。正如我认为转换是一个老笑话,转换基于 2/7/1 规则并以对数 space.

完成
    formats.perceptual = {
        convert (r, g, b) { bin[j++] = Math.sqrt(r * r * 0.2 + g * g * 0.7 + b * b * 0.1) | 0},
        dataStore (w, h) { return new Uint8Array(w * h) },
    }

这是基于成年人感知红色、绿色和蓝色 3 种原色并将灰色(亮度)结果与感知颜色亮度相匹配的平均能力。