We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
一般来说,在Web开发中,很少处理二进制数据,所以很多同学对于JavaScript二进制的API比较陌生。
实际上,浏览器提供了一系列API,用来处理非文本的二进制数据,比如图片、音频、视频等等,现在Web的JavaScript有能力处理它们。
我们来看一个例子:
async function pngInfo(url) { const res = await fetch(imageURL); const arrayBuffer = await res.arrayBuffer(); function isPNG(arrayBuffer) { const pngSign = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); const signData = new Uint8Array(arrayBuffer.slice(0, 8)); return pngSign.length === signData.length && pngSign.every((data, i) => data === signData[i]); } function chunks(arrayBuffer) { let offset = 8; const size = arrayBuffer.byteLength; const info = {chunks: [], buffer: arrayBuffer}; do { const view = new DataView(arrayBuffer, offset, 8); const [chunkSize, type] = [view.getUint32(0), view.getUint32(4)]; const chunkType = CHUNK_TYPES.get(type); info.chunks.push([chunkType, chunkSize]); if(chunkType === 'IHDR') { const ihdrView = new DataView(arrayBuffer, offset + 8, chunkSize); const [width, height] = [ihdrView.getInt32(0), ihdrView.getInt32(4)]; const depth = ihdrView.getUint8(8), colorType = ihdrView.getUint8(9), compressionMethod = ihdrView.getUint8(10), filterMethod = ihdrView.getUint8(11), interlaceMethod = ihdrView.getUint8(12); info.meta = {width, height, depth, colorType, compressionMethod, filterMethod, interlaceMethod}; } offset += (8 + chunkSize + 4); } while(offset < size); return info; } const CHUNK_TYPES = new Map([ [0x49484452, 'IHDR'], [0x504c5445, 'PLTE'], [0x74524e53, 'TRNS'], [0x73524742, 'sRGB'], [0x67414d41, 'GAMA'], [0x49444154, 'IDAT'], [0x49454e44, 'IEND'], ]); if(isPNG(arrayBuffer)) { const view = new DataView(arrayBuffer); const data = new Uint8Array(view.buffer); const info = chunks(arrayBuffer); return info; } return null; }; const imageURL = 'https://p1.ssl.qhimg.com/t011f60e5399df3d7a6.png'; pngInfo(imageURL).then((info) => { console.log(info); });
上面这段代码读取一个PNG文件并获取图片的meta信息。
Fetch API 请求返回的对象中,可以通过 arrayBuffer() 方法拿到二进制的 ArrayBuffer 数据。
const res = await fetch(imageURL); const arrayBuffer = await res.arrayBuffer();
👉🏻 【知识点】:ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区。ArrayBuffer 不能直接操作,而是要通过 TypedArray 或 DataView 对象来操作,它们会将缓冲区中的数据表示为特定的格式,并通过这些格式来读写缓冲区的内容。
PNG图片的格式是分块的数据,但是它前8个字节是签名,固定为:[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]
[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]
所以我们可以通过它来检验这个数据格式是否是合法的PNG图片:
function isPNG(arrayBuffer) { const pngSign = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); const signData = new Uint8Array(arrayBuffer.slice(0, 8)); return pngSign.length === signData.length && pngSign.every((data, i) => data === signData[i]); }
接下来,数据分为若干Chunk,有不同的类型,但是每个Chunk的前8个字节是块内容的字节数和Chunk类型,末4个字节是校验码。
PNG的Chunk有很多种类型,这里不一一介绍,其中我们关心的是第一个Chunk,它的类型必须是IHDR,长度为13个字节,存放图片的meta信息,包括以下信息:
IHDR
我们就可以将这些信息读取出来:
function chunks(arrayBuffer) { let offset = 8; const size = arrayBuffer.byteLength; const info = {chunks: [], buffer: arrayBuffer}; do { const view = new DataView(arrayBuffer, offset, 8); const [chunkSize, type] = [view.getUint32(0), view.getUint32(4)]; const chunkType = CHUNK_TYPES.get(type); info.chunks.push([chunkType, chunkSize]); if(chunkType === 'IHDR') { const ihdrView = new DataView(arrayBuffer, offset + 8, chunkSize); const [width, height] = [ihdrView.getInt32(0), ihdrView.getInt32(4)]; const depth = ihdrView.getUint8(8), colorType = ihdrView.getUint8(9), compressionMethod = ihdrView.getUint8(10), filterMethod = ihdrView.getUint8(11), interlaceMethod = ihdrView.getUint8(12); info.meta = {width, height, depth, colorType, compressionMethod, filterMethod, interlaceMethod}; } offset += (8 + chunkSize + 4); } while(offset < size); return info; }
由于信息的长度是不一致的,有的是4个字节,有的是1个字节,在这里,我们通过DataView来操作:
// 以起始位置offset创建DataView const view = new DataView(arrayBuffer, offset, 8); // 起始8个字节中,前4个字节是内容长度 const [chunkSize, type] = [view.getUint32(0), view.getUint32(4)];
// 我们检查chunkType const chunkType = CHUNK_TYPES.get(type); info.chunks.push([chunkType, chunkSize]); // 如果是meta信息的话,chunkType是IHDR if(chunkType === 'IHDR') { // 创建新的DataView,起始位置为offset往后8个字节,长度为chunkSize const ihdrView = new DataView(arrayBuffer, offset + 8, chunkSize); // 头8个字节是width和height,每个都是4字节,所以用getInt32 const [width, height] = [ihdrView.getInt32(0), ihdrView.getInt32(4)]; // 后续的信息都是1个字节,所以用getUnit8 const depth = ihdrView.getUint8(8), colorType = ihdrView.getUint8(9), compressionMethod = ihdrView.getUint8(10), filterMethod = ihdrView.getUint8(11), interlaceMethod = ihdrView.getUint8(12); info.meta = {width, height, depth, colorType, compressionMethod, filterMethod, interlaceMethod}; }
这样我们就获得了图片meta数据,数据如下:
{ "chunks":[["IHDR",13],["sRGB",1],["IDAT",14000],["IEND",0]], "meta":{ "width":400, "height":400, "depth":8, "colorType":6, "compressionMethod":0, "filterMethod":0, "interlaceMethod":0 } }
PNG图片的压缩数据在"IDAT"类型的Chunk中,如果是在服务器上,我们可以通过zlib.inflate把数据解压出来。但是我们在浏览器上,是没办法直接解压数据的。不过我们可以将图片绘制到canvas上,然后通过canvas的getImageData API来读取数据,改写完成之后,将再通过putImageData将它写回canvas中,最后通过canvas.toDataURL()将图片数据转成base64,然后写到Image对象的src中去,这样就将处理好的图片显示出来了。
"IDAT"
zlib.inflate
const imageURL = 'https://p1.ssl.qhimg.com/t011f60e5399df3d7a6.png'; pngInfo(imageURL).then((info) => { return createImageBitmap(new Blob([info.buffer])); }).then((bitmap) => { const canvas = document.createElement('canvas'); canvas.width = bitmap.width; canvas.height = bitmap.height; const context = canvas.getContext('2d'); context.drawImage(bitmap, 0, 0); const imageData = context.getImageData(0, 0, canvas.width, canvas.height); const data = imageData.data; for(let i = 0; i < data.length; i += 4) { const r = data[i]; const g = data[i + 1]; const b = data[i + 2]; const grey = Math.floor((r + g + b) / 3); data[i] = data[i + 1] = data[i + 2] = grey; } context.putImageData(imageData, 0, 0); const image = new Image(); image.src = canvas.toDataURL(); document.body.appendChild(image); });
在这里,我通过createImageBit用buffer数据创建ImageBitmap,当然,你也可以直接将原始图片放到一个Image对象里丢给canvas去绘制,不过我们已经拿到了buffer数据,创建ImageBitmap,canvas处理起来会快一点。
注意我们使用了一个Blob对象,Blob也是浏览器处理二进制数据的一个API。
👉🏻 Blob 是 Binary Large Object 的缩写,Blob 对象表示一个不可变、原始数据的类文件对象。
其实Blob也很有用,我们可以通过它来处理图片,比如压缩图片,关于这部分内容,留待下一次讨论。
The text was updated successfully, but these errors were encountered:
赞一个
Sorry, something went wrong.
No branches or pull requests
一般来说,在Web开发中,很少处理二进制数据,所以很多同学对于JavaScript二进制的API比较陌生。
实际上,浏览器提供了一系列API,用来处理非文本的二进制数据,比如图片、音频、视频等等,现在Web的JavaScript有能力处理它们。
我们来看一个例子:
上面这段代码读取一个PNG文件并获取图片的meta信息。
Fetch API 请求返回的对象中,可以通过 arrayBuffer() 方法拿到二进制的 ArrayBuffer 数据。
👉🏻 【知识点】:ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区。ArrayBuffer 不能直接操作,而是要通过 TypedArray 或 DataView 对象来操作,它们会将缓冲区中的数据表示为特定的格式,并通过这些格式来读写缓冲区的内容。
PNG图片的格式是分块的数据,但是它前8个字节是签名,固定为:
[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]
所以我们可以通过它来检验这个数据格式是否是合法的PNG图片:
接下来,数据分为若干Chunk,有不同的类型,但是每个Chunk的前8个字节是块内容的字节数和Chunk类型,末4个字节是校验码。
PNG的Chunk有很多种类型,这里不一一介绍,其中我们关心的是第一个Chunk,它的类型必须是
IHDR
,长度为13个字节,存放图片的meta信息,包括以下信息:我们就可以将这些信息读取出来:
由于信息的长度是不一致的,有的是4个字节,有的是1个字节,在这里,我们通过DataView来操作:
这样我们就获得了图片meta数据,数据如下:
扩展
PNG图片的压缩数据在
"IDAT"
类型的Chunk中,如果是在服务器上,我们可以通过zlib.inflate
把数据解压出来。但是我们在浏览器上,是没办法直接解压数据的。不过我们可以将图片绘制到canvas上,然后通过canvas的getImageData API来读取数据,改写完成之后,将再通过putImageData将它写回canvas中,最后通过canvas.toDataURL()将图片数据转成base64,然后写到Image对象的src中去,这样就将处理好的图片显示出来了。在这里,我通过createImageBit用buffer数据创建ImageBitmap,当然,你也可以直接将原始图片放到一个Image对象里丢给canvas去绘制,不过我们已经拿到了buffer数据,创建ImageBitmap,canvas处理起来会快一点。
注意我们使用了一个Blob对象,Blob也是浏览器处理二进制数据的一个API。
👉🏻 Blob 是 Binary Large Object 的缩写,Blob 对象表示一个不可变、原始数据的类文件对象。
其实Blob也很有用,我们可以通过它来处理图片,比如压缩图片,关于这部分内容,留待下一次讨论。
The text was updated successfully, but these errors were encountered: