Skip to content

Commit

Permalink
Switch to using new png fonts + .osd file format v2 support: (#420)
Browse files Browse the repository at this point in the history
Switch to using new png fonts + .osd file format v2 support:

- Use PNG font files instead of the old bin format
- Only 2 fonts now; one for sd + one for hd, rather than separate pages
- But the PNG can have equivalent of 4 pages; so BF 4.5 colour is supported
- OSD v2 support; which uses a 4 char string (BTFL/INAV etc) as the FC identifier. Can still handle v1 files too
- Font files now optional; it will use the FC variant to load defaults from the msp-osd repo
  • Loading branch information
benlumley authored May 16, 2024
1 parent 16913d6 commit d45f322
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 80 deletions.
42 changes: 10 additions & 32 deletions src/features/osd-overlay/FileDrop.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -126,7 +116,7 @@ export default function FileDrop(props) {
}}
>
<input
accept=".mp4,.osd,.bin"
accept=".mp4,.osd,.png"
multiple
onChange={handleFileChange}
ref={inputRef}
Expand Down Expand Up @@ -158,27 +148,15 @@ export default function FileDrop(props) {
/>

<FileDropEntry
file={files.fontFileSd1}
file={files.fontFileSd}
icon={FontIcon}
label={t("fileDropFontSd1")}
label={t("fileDropFontSd")}
/>

<FileDropEntry
file={files.fontFileSd2}
file={files.fontFileHd}
icon={FontIcon}
label={t("fileDropFontSd2")}
/>

<FileDropEntry
file={files.fontFileHd1}
icon={FontIcon}
label={t("fileDropFontHd1")}
/>

<FileDropEntry
file={files.fontFileHd2}
icon={FontIcon}
label={t("fileDropFontHd2")}
label={t("fileDropFontHd")}
/>
</Stack>
</Paper>
Expand Down
10 changes: 2 additions & 8 deletions src/features/osd-overlay/OsdOverlay.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
71 changes: 43 additions & 28 deletions src/osd-overlay/fonts.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { OsdReader } from "./osd";
export const SD_TILE_WIDTH = 12 * 3;
export const SD_TILE_HEIGHT = 18 * 3;

Expand All @@ -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 {
Expand All @@ -33,35 +30,53 @@ export class Font {
return this.tiles[index];
}

static async fromFile(file: File): Promise<Font> {
const data = await file.arrayBuffer();
const isHd = file.name.includes("hd");
static async fromFile(file: File, isHd : boolean, reader: OsdReader): Promise<Font> {
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<FontPack> {
static async fromFiles(files: FontPackFiles, reader: OsdReader): Promise<FontPack> {
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),
};
}
}
60 changes: 59 additions & 1 deletion src/osd-overlay/osd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down
15 changes: 4 additions & 11 deletions src/osd-overlay/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
Font,
FontPack,
FontPackFiles,
TILES_PER_PAGE,
} from "./fonts";
import { OsdReader } from "./osd";
import { SrtReader } from "./srt";
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
);
Expand Down
2 changes: 2 additions & 0 deletions src/translations/en/osdOverlay.json
Original file line number Diff line number Diff line change
Expand Up @@ -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)",
Expand Down

0 comments on commit d45f322

Please sign in to comment.