From 96c7b541a435ea1d3da208a572dd4b5f6b7dde70 Mon Sep 17 00:00:00 2001 From: fpv-wtf <> Date: Thu, 16 May 2024 11:04:50 +0000 Subject: [PATCH] Update from https://github.com/fpv-wtf/wtfos-configurator/commit/d45f3224a44365362a11673d3733a567e55ae300 --- src/features/osd-overlay/FileDrop.jsx | 42 ++++----------- src/features/osd-overlay/OsdOverlay.jsx | 10 +--- src/osd-overlay/fonts.ts | 71 +++++++++++++++---------- src/osd-overlay/osd.ts | 60 ++++++++++++++++++++- src/osd-overlay/worker.ts | 15 ++---- src/translations/en/osdOverlay.json | 2 + 6 files changed, 120 insertions(+), 80 deletions(-) diff --git a/src/features/osd-overlay/FileDrop.jsx b/src/features/osd-overlay/FileDrop.jsx index 7c32f98..f617ff4 100644 --- a/src/features/osd-overlay/FileDrop.jsx +++ b/src/features/osd-overlay/FileDrop.jsx @@ -16,10 +16,8 @@ import FileDropEntry from "./FileDropEntry"; export function useFileDropState() { const [files, setFiles] = React.useState({ - fontFileHd1: null, - fontFileHd2: null, - fontFileSd1: null, - fontFileSd2: null, + fontFileHd: null, + fontFileSd: null, osdFile: null, srtFile: null, videoFile: null, @@ -60,19 +58,11 @@ export default function FileDrop(props) { changedFiles.srtFile = file; break; - case "bin": + case "png": if (name.includes("hd")) { - if (name.includes("_2")) { - changedFiles.fontFileHd2 = file; - } else { - changedFiles.fontFileHd1 = file; - } + changedFiles.fontFileHd = file; } else { - if (name.includes("_2")) { - changedFiles.fontFileSd2 = file; - } else { - changedFiles.fontFileSd1 = file; - } + changedFiles.fontFileSd = file; } break; @@ -126,7 +116,7 @@ export default function FileDrop(props) { }} > - - - - diff --git a/src/features/osd-overlay/OsdOverlay.jsx b/src/features/osd-overlay/OsdOverlay.jsx index f84b70b..9a9f147 100644 --- a/src/features/osd-overlay/OsdOverlay.jsx +++ b/src/features/osd-overlay/OsdOverlay.jsx @@ -30,10 +30,8 @@ export default function OsdOverlay() { const osdFile = files.osdFile; const srtFile = files.srtFile; const fontFiles = React.useMemo(() => ({ - sd1: files.fontFileSd1, - sd2: files.fontFileSd2, - hd1: files.fontFileHd1, - hd2: files.fontFileHd2, + sd: files.fontFileSd, + hd: files.fontFileHd, }), [files]); const [progress, setProgress] = React.useState(0); @@ -69,10 +67,6 @@ export default function OsdOverlay() { const startEnabled = ( videoFile && osdFile && - fontFiles.sd1 && - fontFiles.sd2 && - fontFiles.hd1 && - fontFiles.hd2 && !inProgress ); const progressValue = progressMax ? (progress / progressMax) * 100 : 0; diff --git a/src/osd-overlay/fonts.ts b/src/osd-overlay/fonts.ts index e150fe9..09e5516 100644 --- a/src/osd-overlay/fonts.ts +++ b/src/osd-overlay/fonts.ts @@ -1,3 +1,4 @@ +import { OsdReader } from "./osd"; export const SD_TILE_WIDTH = 12 * 3; export const SD_TILE_HEIGHT = 18 * 3; @@ -7,17 +8,13 @@ export const HD_TILE_HEIGHT = 18 * 2; export const TILES_PER_PAGE = 256; export interface FontPack { - sd1: Font; - sd2: Font; - hd1: Font; - hd2: Font; + sd: Font; + hd: Font; } export interface FontPackFiles { - sd1: File; - sd2: File; - hd1: File; - hd2: File; + sd: File; + hd: File; } export class Font { @@ -33,35 +30,53 @@ export class Font { return this.tiles[index]; } - static async fromFile(file: File): Promise { - const data = await file.arrayBuffer(); - const isHd = file.name.includes("hd"); + static async fromFile(file: File, isHd : boolean, reader: OsdReader): Promise { + const [filename, data] = await (async (file : File) => { + if (file && file.size > 0) { + return [file.name, await file.arrayBuffer()]; + } else { + const font_filename = `font_${reader.header.config.fontVariant.toLowerCase()}${isHd ? "_hd" : ""}.png`; + return ["font_filename", await fetch(`https://raw.githubusercontent.com/fpv-wtf/msp-osd/main/fonts/${font_filename}`).then((response) => response.arrayBuffer())]; + } + })(file); const tileWidth = isHd ? HD_TILE_WIDTH : SD_TILE_WIDTH; const tileHeight = isHd ? HD_TILE_HEIGHT : SD_TILE_HEIGHT; - const tiles: ImageBitmap[] = []; - for (let tileIndex = 0; tileIndex < TILES_PER_PAGE; tileIndex++) { - const pixData = new Uint8ClampedArray( - data, - tileIndex * tileWidth * tileHeight * 4, - tileWidth * tileHeight * 4 - ); - - const imageData = new ImageData(pixData, tileWidth, tileHeight); - const imageBitmap = await createImageBitmap(imageData); - tiles.push(imageBitmap); + // Create an image bitmap from the ArrayBuffer + const imageBitmap = await createImageBitmap(new Blob([data])); + + const canvas = new OffscreenCanvas(imageBitmap.width, imageBitmap.height); + const context = canvas.getContext("2d"); + + if (!context) { + throw new Error("2D context not supported or canvas creation failed"); + } + + context.drawImage(imageBitmap, 0, 0); + + const tiles = []; + const tilesPerColumn = TILES_PER_PAGE; // Number of tiles per column + const columns = imageBitmap.width / tileWidth; // Number of columns + + for (let columnIndex = 0; columnIndex < columns; columnIndex++) { + for (let rowIndex = 0; rowIndex < tilesPerColumn; rowIndex++) { + const x = columnIndex * tileWidth; // x-coordinate based on column + const y = rowIndex * tileHeight; // y-coordinate based on row + + const imageData = context.getImageData(x, y, tileWidth, tileHeight); + const tileBitmap = await createImageBitmap(imageData); + tiles.push(tileBitmap); + } } - return new Font(file.name, tiles); + return new Font(filename, tiles); } - static async fromFiles(files: FontPackFiles): Promise { + static async fromFiles(files: FontPackFiles, reader: OsdReader): Promise { return { - sd1: await Font.fromFile(files.sd1), - sd2: await Font.fromFile(files.sd2), - hd1: await Font.fromFile(files.hd1), - hd2: await Font.fromFile(files.hd2), + sd: await Font.fromFile(files.sd, false, reader), + hd: await Font.fromFile(files.hd, true, reader), }; } } diff --git a/src/osd-overlay/osd.ts b/src/osd-overlay/osd.ts index 284c9c1..7cc3918 100644 --- a/src/osd-overlay/osd.ts +++ b/src/osd-overlay/osd.ts @@ -7,6 +7,22 @@ interface OsdHeader { } interface OsdConfig { + charWidth: number; + charHeight: number; + fontWidth: number; + fontHeight: number; + xOffset: number; + yOffset: number; + fontVariant: string; +} + +interface OsdHeaderV1 { + magic: string; + version: number; + config: OsdConfigV1; +} + +interface OsdConfigV1 { charWidth: number; charHeight: number; fontWidth: number; @@ -38,10 +54,52 @@ export class OsdReader { fontHeight: stream.getNextUint8(), xOffset: stream.getNextUint16(), yOffset: stream.getNextUint16(), - fontVariant: stream.getNextUint8(), + fontVariant: stream.getNextString(5).substring(0, 4), // read 5 bytes, keep 4. string is from c; null terminated. reading all 5 leaves pointer in right place to start reading frames below }, }; + // v1 of this format used a slightly different structure - fontVariant was a number, not a string + // in msp-osd itself an enum was used to store the FC variant, which became the number we have here + // since msp-osd 0.12 we use the FC identifier internally (so we don't need to rely on the magic enum) + // this maps the legacy enum to the correct string identifier, as well as leaving the file pointer + // in the correct place for a legacy file + if (this.header.version === 1) { + stream.resetOffset(); + const tempheader : OsdHeaderV1 = { + magic: stream.getNextString(7), + version: stream.getNextUint16(), + config: { + charWidth: stream.getNextUint8(), + charHeight: stream.getNextUint8(), + fontWidth: stream.getNextUint8(), + fontHeight: stream.getNextUint8(), + xOffset: stream.getNextUint16(), + yOffset: stream.getNextUint16(), + fontVariant: stream.getNextUint8(), + }, + }; + + switch (tempheader.config.fontVariant) { + case 1: // FONT_VARIANT_BETAFLIGHT + this.header.config.fontVariant = "BTFL"; + break; + case 2: // FONT_VARIANT_INAV + this.header.config.fontVariant = "INAV"; + break; + case 3: // FONT_VARIANT_ARDUPILOT + this.header.config.fontVariant = "ARDU"; + break; + case 4: // FONT_VARIANT_KISS_ULTRA + this.header.config.fontVariant = "ULTR"; + break; + case 5: // FONT_VARIANT_QUICKSILVER + this.header.config.fontVariant = "QUIC"; + break; + default: + this.header.config.fontVariant = ""; // Empty string for unknown variant + } + } + if (this.header.config.charWidth === 31) { this.header.config.charWidth = 30; } diff --git a/src/osd-overlay/worker.ts b/src/osd-overlay/worker.ts index 8942dc1..5f193c7 100644 --- a/src/osd-overlay/worker.ts +++ b/src/osd-overlay/worker.ts @@ -6,7 +6,6 @@ import { Font, FontPack, FontPackFiles, - TILES_PER_PAGE, } from "./fonts"; import { OsdReader } from "./osd"; import { SrtReader } from "./srt"; @@ -65,7 +64,7 @@ export class VideoWorker { this.srtReader = await SrtReader.fromFile(options.srtFile); } - this.fontPack = await Font.fromFiles(options.fontFiles); + this.fontPack = await Font.fromFiles(options.fontFiles, this.osdReader); const { width, @@ -192,19 +191,13 @@ export class VideoWorker { let font: Font; if (this.hd) { - font = - osdFrameChar < TILES_PER_PAGE - ? this.fontPack!.hd1 - : this.fontPack!.hd2; + font = this.fontPack!.hd; } else { - font = - osdFrameChar < TILES_PER_PAGE - ? this.fontPack!.sd1 - : this.fontPack!.sd2; + font = this.fontPack!.sd; } osdCtx.drawImage( - font.getTile(osdFrameChar % TILES_PER_PAGE), + font.getTile(osdFrameChar), x * this.osdReader!.header.config.fontWidth, y * this.osdReader!.header.config.fontHeight ); diff --git a/src/translations/en/osdOverlay.json b/src/translations/en/osdOverlay.json index bbdf35f..0dae800 100644 --- a/src/translations/en/osdOverlay.json +++ b/src/translations/en/osdOverlay.json @@ -17,6 +17,8 @@ "fileDropFontHd2": "Font 2 (HD)", "fileDropFontSd1": "Font 1 (SD)", "fileDropFontSd2": "Font 2 (SD)", + "fileDropFontHd": "Custom HD Font (optional)", + "fileDropFontSd": "Custom SD Font (optional)", "fileDropHelp": "You can drop any of your files here, or click to select them individually.", "fileDropOsd": "OSD", "fileDropSrt": "SRT (optional)",