Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
export type CameraImageType = 'rgb565le' | 'rgb565be' | 'yuv422' | 'jpeg'
export type CameraFrame = {
width: number
height: number
imageType: CameraImageType
buffer: ArrayBuffer
close?: () => void
}
export type MosaicBlock = {
x: number
y: number
width: number
height: number
color: number
}
export type MosaicOptions = {
width: number
height: number
blockSize: number
}
export type FrameCopyOptions = {
width: number
height: number
byteOrder?: 'le' | 'be'
}
const HEX_DIGITS = '0123456789abcdef'
function hexByte(value: number): string {
const byte = value & 0xff
return `${HEX_DIGITS[(byte >> 4) & 0x0f]}${HEX_DIGITS[byte & 0x0f]}`
}
export function toPiuColorString(color: number): string {
return `#${hexByte(color >> 16)}${hexByte(color >> 8)}${hexByte(color)}`
}
function isRgb565ImageType(imageType: CameraImageType): imageType is 'rgb565le' | 'rgb565be' {
return imageType === 'rgb565le' || imageType === 'rgb565be'
}
function byteOrderForImageType(imageType: CameraImageType): 'le' | 'be' {
return imageType === 'rgb565be' ? 'be' : 'le'
}
export function rgb565ToPiuColor(buffer: ArrayBuffer, byteOffset: number, byteOrder: 'le' | 'be' = 'le'): number {
const view = new Uint8Array(buffer)
const pixel =
byteOrder === 'be' ? (view[byteOffset] << 8) | view[byteOffset + 1] : view[byteOffset] | (view[byteOffset + 1] << 8)
const red5 = (pixel >> 11) & 0x1f
const green6 = (pixel >> 5) & 0x3f
const blue5 = pixel & 0x1f
const red = (red5 << 3) | (red5 >> 2)
const green = (green6 << 2) | (green6 >> 4)
const blue = (blue5 << 3) | (blue5 >> 2)
return (red << 16) | (green << 8) | blue
}
export function rgb565LeToPiuColor(buffer: ArrayBuffer, byteOffset: number): number {
return rgb565ToPiuColor(buffer, byteOffset, 'le')
}
function clampSampleCoordinate(value: number, maxExclusive: number): number {
const coordinate = value | 0
if (coordinate < 0) return 0
if (coordinate >= maxExclusive) return maxExclusive - 1
return coordinate
}
export function sampleRgb565Mosaic(frame: CameraFrame, options: MosaicOptions): MosaicBlock[] {
if (!isRgb565ImageType(frame.imageType) || frame.width <= 0 || frame.height <= 0) {
return []
}
const byteOrder = byteOrderForImageType(frame.imageType)
const blockSize = Math.max(1, options.blockSize | 0)
const targetWidth = Math.max(1, options.width | 0)
const targetHeight = Math.max(1, options.height | 0)
const blocks: MosaicBlock[] = []
for (let y = 0; y < targetHeight; y += blockSize) {
const height = Math.min(blockSize, targetHeight - y)
const sampleY = clampSampleCoordinate(((y + height / 2) * frame.height) / targetHeight, frame.height)
for (let x = 0; x < targetWidth; x += blockSize) {
const width = Math.min(blockSize, targetWidth - x)
const sampleX = clampSampleCoordinate(((x + width / 2) * frame.width) / targetWidth, frame.width)
const byteOffset = (sampleY * frame.width + sampleX) * 2
blocks.push({
x,
y,
width,
height,
color: rgb565ToPiuColor(frame.buffer, byteOffset, byteOrder),
})
}
}
return blocks
}
export function sampleRgb565LeMosaic(frame: CameraFrame, options: MosaicOptions): MosaicBlock[] {
return frame.imageType === 'rgb565le' ? sampleRgb565Mosaic(frame, options) : []
}
export function copyRgb565Frame(frame: CameraFrame, options: FrameCopyOptions): ArrayBuffer {
const targetWidth = Math.max(1, options.width | 0)
const targetHeight = Math.max(1, options.height | 0)
const targetByteOrder = options.byteOrder ?? byteOrderForImageType(frame.imageType)
const sourceByteOrder = byteOrderForImageType(frame.imageType)
const swapBytes = sourceByteOrder !== targetByteOrder
const target = new Uint8Array(targetWidth * targetHeight * 2)
if (!isRgb565ImageType(frame.imageType) || frame.width <= 0 || frame.height <= 0) {
return target.buffer
}
const source = new Uint8Array(frame.buffer)
const requiredBytes = frame.width * frame.height * 2
if (source.byteLength < requiredBytes) {
return target.buffer
}
for (let y = 0; y < targetHeight; y += 1) {
const sourceY = clampSampleCoordinate((y * frame.height) / targetHeight, frame.height)
const targetRow = y * targetWidth * 2
const sourceRow = sourceY * frame.width * 2
for (let x = 0; x < targetWidth; x += 1) {
const sourceX = clampSampleCoordinate((x * frame.width) / targetWidth, frame.width)
const sourceOffset = sourceRow + sourceX * 2
const targetOffset = targetRow + x * 2
if (swapBytes) {
target[targetOffset] = source[sourceOffset + 1]
target[targetOffset + 1] = source[sourceOffset]
} else {
target[targetOffset] = source[sourceOffset]
target[targetOffset + 1] = source[sourceOffset + 1]
}
}
}
return target.buffer
}
export function copyRgb565LeFrame(frame: CameraFrame, options: FrameCopyOptions): ArrayBuffer {
if (frame.imageType === 'rgb565le') return copyRgb565Frame(frame, options)
const targetWidth = Math.max(1, options.width | 0)
const targetHeight = Math.max(1, options.height | 0)
return new ArrayBuffer(targetWidth * targetHeight * 2)
}
0454f9efbThis file is pinned to the selected release. Download the original to open it in your design tool.