Skip to content
New issue

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

Switch to using new png fonts + .osd file format v2 support: #420

Merged
merged 2 commits into from
May 16, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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),
};
}
}
61 changes: 60 additions & 1 deletion src/osd-overlay/osd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,30 @@ 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;
fontHeight: number;
xOffset: number;
yOffset: number;
fontVariant: number;

benlumley marked this conversation as resolved.
Show resolved Hide resolved
}

interface OsdFrame {
Expand All @@ -38,10 +55,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
Loading