mirror of
https://github.com/betaflight/betaflight-configurator.git
synced 2025-07-23 16:25:22 +03:00
add new option to customize OSD logo
This commit is contained in:
parent
48ee8fea78
commit
41c46afcc5
3 changed files with 188 additions and 7 deletions
|
@ -2786,6 +2786,15 @@
|
||||||
"osdSetupOpenFont": {
|
"osdSetupOpenFont": {
|
||||||
"message": "Open Font File"
|
"message": "Open Font File"
|
||||||
},
|
},
|
||||||
|
"osdSetupLogo": {
|
||||||
|
"message": "Logo in font:"
|
||||||
|
},
|
||||||
|
"osdSetupReplaceLogo": {
|
||||||
|
"message": "Replace Logo"
|
||||||
|
},
|
||||||
|
"osdSetupReplaceLogoHelp": {
|
||||||
|
"message": "Customized logo image has to be 288×72 pixels in size containing black and white pixels only on a completely green background."
|
||||||
|
},
|
||||||
"osdSetupUploadFont": {
|
"osdSetupUploadFont": {
|
||||||
"message": "Upload Font"
|
"message": "Upload Font"
|
||||||
},
|
},
|
||||||
|
|
|
@ -57,6 +57,7 @@ FONT.initData = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
FONT.constants = {
|
FONT.constants = {
|
||||||
|
MAX_CHAR_COUNT: 256,
|
||||||
SIZES: {
|
SIZES: {
|
||||||
/** NVM ram size for one font char, actual character bytes **/
|
/** NVM ram size for one font char, actual character bytes **/
|
||||||
MAX_NVM_FONT_CHAR_SIZE: 54,
|
MAX_NVM_FONT_CHAR_SIZE: 54,
|
||||||
|
@ -74,7 +75,21 @@ FONT.constants = {
|
||||||
1: 'rgba(255, 255, 255, 0)',
|
1: 'rgba(255, 255, 255, 0)',
|
||||||
// white
|
// white
|
||||||
2: 'rgba(255,255,255, 1)'
|
2: 'rgba(255,255,255, 1)'
|
||||||
}
|
},
|
||||||
|
LOGO: {
|
||||||
|
TILES_NUM_HORIZ: 24,
|
||||||
|
TILES_NUM_VERT: 4,
|
||||||
|
MCM_COLORMAP: {
|
||||||
|
// background
|
||||||
|
'0-255-0': '01',
|
||||||
|
// black
|
||||||
|
'0-0-0': '00',
|
||||||
|
// white
|
||||||
|
'255-255-255': '10',
|
||||||
|
// fallback
|
||||||
|
'default': '01',
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -123,7 +138,6 @@ FONT.parseMCMFontFile = function(data) {
|
||||||
return FONT.data.characters;
|
return FONT.data.characters;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
FONT.openFontFile = function($preview) {
|
FONT.openFontFile = function($preview) {
|
||||||
return new Promise(function(resolve) {
|
return new Promise(function(resolve) {
|
||||||
chrome.fileSystem.chooseEntry({type: 'openFile', accepts: [{extensions: ['mcm']}]}, function (fileEntry) {
|
chrome.fileSystem.chooseEntry({type: 'openFile', accepts: [{extensions: ['mcm']}]}, function (fileEntry) {
|
||||||
|
@ -149,6 +163,129 @@ FONT.openFontFile = function($preview) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// show a file open dialog and yield an Image object
|
||||||
|
var openLogoImage = function() {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
var validateImage = function(img) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
var expectedWidth = FONT.constants.SIZES.CHAR_WIDTH
|
||||||
|
* FONT.constants.LOGO.TILES_NUM_HORIZ,
|
||||||
|
expectedHeight = FONT.constants.SIZES.CHAR_HEIGHT
|
||||||
|
* FONT.constants.LOGO.TILES_NUM_VERT;
|
||||||
|
if (img.width != expectedWidth || img.height != expectedHeight) {
|
||||||
|
reject("invalid image size; expected "
|
||||||
|
+ expectedWidth + "×" + expectedHeight + " instead of "
|
||||||
|
+ img.width + "×" + img.height);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var canvas = document.createElement('canvas'),
|
||||||
|
ctx = canvas.getContext('2d');
|
||||||
|
canvas.width = img.width;
|
||||||
|
canvas.height = img.height;
|
||||||
|
ctx.drawImage(img, 0, 0);
|
||||||
|
for (var y = 0, Y = canvas.height; y < Y; y++) {
|
||||||
|
for (var x = 0, X = canvas.width; x < X; x++) {
|
||||||
|
var rgbPixel = ctx.getImageData(x, y, 1, 1).data.slice(0, 3),
|
||||||
|
colorKey = rgbPixel.join("-");
|
||||||
|
if (!FONT.constants.LOGO.MCM_COLORMAP[colorKey]) {
|
||||||
|
reject("invalid color palette");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
chrome.fileSystem.chooseEntry({type: 'openFile', accepts: [{extensions: ['png', 'bmp']}]}, function(fileEntry) {
|
||||||
|
if (chrome.runtime.lastError) {
|
||||||
|
console.error(chrome.runtime.lastError.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var img = new Image();
|
||||||
|
img.onload = function() {
|
||||||
|
validateImage(img).then(function() {
|
||||||
|
resolve(img);
|
||||||
|
}).catch(function(error) {
|
||||||
|
console.error(error);
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
img.onerror = function(error) {
|
||||||
|
reject(error);
|
||||||
|
};
|
||||||
|
fileEntry.file(function(file) {
|
||||||
|
img.src = "file://" + file.path;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// replaces the logo in the font based on an Image object
|
||||||
|
FONT.replaceLogoFromImage = function(img) {
|
||||||
|
// takes image data from an ImageData object and returns an MCM symbol as an array of strings
|
||||||
|
var imageToCharacter = function(data) {
|
||||||
|
var char = [],
|
||||||
|
line = "";
|
||||||
|
for (var i = 0, I = data.length; i < I; i += 4) {
|
||||||
|
var rgbPixel = data.slice(i, i + 3),
|
||||||
|
colorKey = rgbPixel.join("-");
|
||||||
|
line += FONT.constants.LOGO.MCM_COLORMAP[colorKey]
|
||||||
|
|| FONT.constants.LOGO.MCM_COLORMAP['default'];
|
||||||
|
if (line.length == 8) {
|
||||||
|
char.push(line);
|
||||||
|
line = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var fieldSize = FONT.constants.SIZES.MAX_NVM_FONT_CHAR_FIELD_SIZE;
|
||||||
|
if (char.length < fieldSize) {
|
||||||
|
var pad = FONT.constants.LOGO.MCM_COLORMAP['default'].repeat(4);
|
||||||
|
for (var i = 0, I = fieldSize - char.length; i < I; i++)
|
||||||
|
char.push(pad);
|
||||||
|
}
|
||||||
|
return char;
|
||||||
|
};
|
||||||
|
|
||||||
|
// takes an OSD symbol as an array of strings and replaces the in-memory character at charAddress with it
|
||||||
|
var replaceChar = function(lines, charAddress) {
|
||||||
|
var characterBits = [];
|
||||||
|
var characterBytes = [];
|
||||||
|
for (var n = 0, N = lines.length; n < N; n++) {
|
||||||
|
var line = lines[n];
|
||||||
|
for (var y = 0; y < 8; y = y + 2) {
|
||||||
|
var v = parseInt(line.slice(y, y+2), 2);
|
||||||
|
characterBits.push(v);
|
||||||
|
}
|
||||||
|
characterBytes.push(parseInt(line, 2));
|
||||||
|
}
|
||||||
|
FONT.data.characters[charAddress] = characterBits;
|
||||||
|
FONT.data.characters_bytes[charAddress] = characterBytes;
|
||||||
|
FONT.data.character_image_urls[charAddress] = null;
|
||||||
|
FONT.draw(charAddress);
|
||||||
|
};
|
||||||
|
|
||||||
|
// loop through an image and replace font symbols
|
||||||
|
var canvas = document.createElement('canvas'),
|
||||||
|
ctx = canvas.getContext('2d'),
|
||||||
|
charAddr = SYM.LOGO;
|
||||||
|
canvas.width = img.width;
|
||||||
|
canvas.height = img.height;
|
||||||
|
ctx.drawImage(img, 0, 0);
|
||||||
|
for (var y = 0; y < FONT.constants.LOGO.TILES_NUM_VERT; y++) {
|
||||||
|
for (var x = 0; x < FONT.constants.LOGO.TILES_NUM_HORIZ; x++) {
|
||||||
|
var imageData = ctx.getImageData(
|
||||||
|
x * FONT.constants.SIZES.CHAR_WIDTH,
|
||||||
|
y * FONT.constants.SIZES.CHAR_HEIGHT,
|
||||||
|
FONT.constants.SIZES.CHAR_WIDTH,
|
||||||
|
FONT.constants.SIZES.CHAR_HEIGHT
|
||||||
|
),
|
||||||
|
newChar = imageToCharacter(imageData.data);
|
||||||
|
replaceChar(newChar, charAddr);
|
||||||
|
charAddr++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns a canvas image with the character on it
|
* returns a canvas image with the character on it
|
||||||
*/
|
*/
|
||||||
|
@ -210,6 +347,16 @@ FONT.preview = function($el) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
FONT.logoPreview = function($el) {
|
||||||
|
$el.empty()
|
||||||
|
.width(FONT.constants.LOGO.TILES_NUM_HORIZ * FONT.constants.SIZES.CHAR_WIDTH)
|
||||||
|
.height(FONT.constants.LOGO.TILES_NUM_VERT * FONT.constants.SIZES.CHAR_HEIGHT);
|
||||||
|
for (var i = SYM.LOGO, I = FONT.constants.MAX_CHAR_COUNT; i < I; i++) {
|
||||||
|
var url = FONT.data.character_image_urls[i];
|
||||||
|
$el.append('<img src="'+url+'" title="0x'+i.toString(16)+'"></img>');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
FONT.symbol = function(hexVal) {
|
FONT.symbol = function(hexVal) {
|
||||||
return String.fromCharCode(hexVal);
|
return String.fromCharCode(hexVal);
|
||||||
};
|
};
|
||||||
|
@ -1232,7 +1379,7 @@ TABS.osd.initialize = function (callback) {
|
||||||
// Open modal window
|
// Open modal window
|
||||||
OSD.GUI.jbox = new jBox('Modal', {
|
OSD.GUI.jbox = new jBox('Modal', {
|
||||||
width: 720,
|
width: 720,
|
||||||
height: 240,
|
height: 420,
|
||||||
closeButton: 'title',
|
closeButton: 'title',
|
||||||
animation: false,
|
animation: false,
|
||||||
attach: $('#fontmanager'),
|
attach: $('#fontmanager'),
|
||||||
|
@ -1687,7 +1834,8 @@ TABS.osd.initialize = function (callback) {
|
||||||
});
|
});
|
||||||
|
|
||||||
// font preview window
|
// font preview window
|
||||||
var $preview = $('.font-preview');
|
var $preview = $('.font-preview'),
|
||||||
|
$logoPreview = $('.logo-preview');
|
||||||
|
|
||||||
// init structs once, also clears current font
|
// init structs once, also clears current font
|
||||||
FONT.initData();
|
FONT.initData();
|
||||||
|
@ -1700,6 +1848,7 @@ TABS.osd.initialize = function (callback) {
|
||||||
$.get('./resources/osd/' + $(this).data('font-file') + '.mcm', function(data) {
|
$.get('./resources/osd/' + $(this).data('font-file') + '.mcm', function(data) {
|
||||||
FONT.parseMCMFontFile(data);
|
FONT.parseMCMFontFile(data);
|
||||||
FONT.preview($preview);
|
FONT.preview($preview);
|
||||||
|
FONT.logoPreview($logoPreview);
|
||||||
updateOsdView();
|
updateOsdView();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1711,6 +1860,7 @@ TABS.osd.initialize = function (callback) {
|
||||||
$fontPicker.removeClass('active');
|
$fontPicker.removeClass('active');
|
||||||
FONT.openFontFile().then(function() {
|
FONT.openFontFile().then(function() {
|
||||||
FONT.preview($preview);
|
FONT.preview($preview);
|
||||||
|
FONT.logoPreview($logoPreview);
|
||||||
updateOsdView();
|
updateOsdView();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1728,6 +1878,18 @@ TABS.osd.initialize = function (callback) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// replace logo
|
||||||
|
$('a.replace_logo').click(function () {
|
||||||
|
if (!GUI.connect_lock) { // button disabled while flashing is in progress
|
||||||
|
openLogoImage().then(function(ctx) {
|
||||||
|
FONT.replaceLogoFromImage(ctx);
|
||||||
|
FONT.logoPreview($logoPreview);
|
||||||
|
}).catch(function(error) {
|
||||||
|
console.error("error loading image:", error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
//Switch all elements
|
//Switch all elements
|
||||||
$('input#switch-all').change(function (event) {
|
$('input#switch-all').change(function (event) {
|
||||||
//if we just change value based on the majority of the switches
|
//if we just change value based on the majority of the switches
|
||||||
|
|
|
@ -122,7 +122,18 @@
|
||||||
<h1 class="tab_title" i18n="osdSetupFontPresets" />
|
<h1 class="tab_title" i18n="osdSetupFontPresets" />
|
||||||
<div class="content_wrapper font-preview"></div>
|
<div class="content_wrapper font-preview"></div>
|
||||||
<div class="fontbuttons">
|
<div class="fontbuttons">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<h1 class="tab_title" i18n="osdSetupLogo" />
|
||||||
|
<div class="helpicon cf_tip" i18n_title="osdSetupReplaceLogoHelp"></div>
|
||||||
|
<div class="content_wrapper" style="background:#00dd00; line-height: 0; margin-bottom:10px">
|
||||||
|
<div class="logo-preview" style="background:#00ff00">
|
||||||
|
<!-- this will be resized at runtime -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="default_btn" style="width:100%; float:left;
|
||||||
|
">
|
||||||
|
<a class="replace_logo" i18n="osdSetupReplaceLogo" />
|
||||||
</div>
|
</div>
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<a name="progressbar"></a>
|
<a name="progressbar"></a>
|
||||||
|
@ -130,7 +141,7 @@
|
||||||
<div class="progressLabel" style="margin-top: -21px; width: 95%; text-align: center; color: white; position: absolute;"></div>
|
<div class="progressLabel" style="margin-top: -21px; width: 95%; text-align: center; color: white; position: absolute;"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="default_btn green" style="width:100%; float:left; margin-bottom: 0px;
|
<div class="default_btn green" style="width:100%; float:left;
|
||||||
">
|
">
|
||||||
<a class="flash_font active" i18n="osdSetupUploadFont" />
|
<a class="flash_font active" i18n="osdSetupUploadFont" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -146,4 +157,3 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue