CloverBootloader/rEFIt_UEFI/libeg/VectorGraphics.cpp
jief 42cece9885 Fix nanosvg leaks.
Move global variable textfaces in XTheme.
Move global variable fontsDB in XTheme.
Remove XTheme member SVGParser. SVGParser is deleted just after use.
Remove XTheme members ImageSVG and ImageSVGnight. All images are
rasterized at load, so no need to keep that.
Remove XIcon setFilled because XIcon knows if it's filled or not by
checking Image & ImageNight
2023-11-08 14:35:22 +01:00

572 lines
19 KiB
C++
Executable File

/*
* Additional procedures for vector theme support
*
* Slice, 2018
*
*/
#define TEST_MATH 0
#define TEST_SVG_IMAGE 1
#define TEST_SIZEOF 0
#define TEST_FONT 0
#define TEST_DITHER 0
#include "VectorGraphics.h"
#include <Platform.h> // Only use angled for Platform, else, xcode project won't compile
#include "nanosvg.h"
#include "FloatLib.h"
#include "lodepng.h"
#include "../refit/screen.h"
#include "../cpp_foundation/XString.h"
#include "../refit/lib.h"
#include "../Settings/Self.h"
#ifndef DEBUG_ALL
#define DEBUG_VEC 1
#else
#define DEBUG_VEC DEBUG_ALL
#endif
#if DEBUG_VEC == 0
#define DBG(...)
#else
#define DBG(...) DebugLog(DEBUG_VEC, __VA_ARGS__)
#endif
#include "XTheme.h"
extern const CHAR8* IconsNames[];
extern const INTN IconsNamesSize;
#define NSVG_RGB(r, g, b) (((unsigned int)b) | ((unsigned int)g << 8) | ((unsigned int)r << 16))
//#define NSVG_RGBA(r, g, b, a) (((unsigned int)b) | ((unsigned int)g << 8) | ((unsigned int)r << 16) | ((unsigned int)a << 24))
extern void
WaitForKeyPress(CHAR16 *Message);
extern void DumpFloat2 (CONST char* s, float* t, int N);
extern UINTN NumFrames;
extern UINTN FrameTime;
EFI_STATUS XTheme::ParseSVGXIcon(NSVGparser* SVGParser, INTN Id, const XString8& IconNameX, OUT XImage* Image)
{
EFI_STATUS Status = EFI_NOT_FOUND;
NSVGimage* SVGimage = SVGParser->image; // full theme SVG image
NSVGshape *shape;
NSVGshape *shapeNext/*, *shapesTail = NULL, *shapePrev*/;
float IconImageWidth = 0; // Width of the image.
float IconImageHeight = 0; // Height of the image.
shape = SVGimage->shapes;
while (shape) {
shapeNext = shape->next;
if ( isShapeInGroup(shape, IconNameX.c_str()) )
{
if (BootCampStyle && IconNameX.contains("selection_big")) {
shape->opacity = 0.f;
}
if (XString8().takeValueFrom(shape->id).contains("BoundingRect")) {
//there is bounds after nsvgParse()
IconImageWidth = shape->bounds[2] - shape->bounds[0];
IconImageHeight = shape->bounds[3] - shape->bounds[1];
// DBG("parsed bounds: %f, %f\n", IconImage.width, IconImage.height);
if ( IconImageHeight < 1.f ) {
IconImageHeight = 200.f;
}
if (IconNameX.contains("selection_big") && (!SelectionOnTop)) {
MainEntriesSize = (int)(IconImageWidth * Scale); //xxx
row0TileSize = MainEntriesSize + (int)(16.f * Scale);
// DBG("main entry size = %lld\n", MainEntriesSize);
}
if (IconNameX.contains("selection_small") && (!SelectionOnTop)) {
row1TileSize = (int)(IconImageWidth * Scale);
}
// not exclude BoundingRect from IconImage?
shape->flags = 0; //invisible
shape = shapeNext;
continue; //while(shape) it is BoundingRect shape
}
shape->flags = NSVG_VIS_VISIBLE;
} //the shape in the group
shape = shapeNext;
} //while shape
if ( IconImageWidth == 0 || IconImageHeight == 0 ) {
return Status;
}
float bounds[4];
nsvg__imageBounds(SVGimage, bounds, IconNameX.c_str());
if ((Id == BUILTIN_ICON_BANNER) && IconNameX.contains("Banner")) {
BannerPosX = (int)(bounds[0] * Scale - CentreShift);
if (BannerPosX < 0) {
BannerPosX = 1; //one pixel
}
BannerPosY = (int)(bounds[1] * Scale);
// DBG("Banner position at parse [%lld,%lld]\n", BannerPosX, BannerPosY);
}
float Height = IconImageHeight * Scale;
float Width = IconImageWidth * Scale;
if (Height < 0 || Width < 0) {
return EFI_NOT_FOUND;
}
// DBG("icon %s width=%f height=%f\n", IconNameX.c_str(), Width, Height);
int iWidth = ((int)(Width+0.5f) + 7) & ~0x07u;
int iHeight = ((int)(Height+0.5f) + 7) & ~0x07u;
XImage NewImage(iWidth, iHeight); //empty
float tx = 0.f, ty = 0.f;
if ((Id != BUILTIN_ICON_BACKGROUND) &&
(Id != BUILTIN_ICON_ANIME) &&
!IconNameX.contains("Banner")) {
float realWidth = (bounds[2] - bounds[0]) * Scale;
float realHeight = (bounds[3] - bounds[1]) * Scale;
// DBG("icon=%s width=%f realwidth=%f\n", IconNameX.c_str(), Width, realWidth);
tx = (Width - realWidth) * 0.5f;
ty = (Height - realHeight) * 0.5f;
}
NSVGrasterizer* rast = nsvgCreateRasterizer();
nsvgRasterize(rast, SVGimage, bounds, IconNameX.c_str(), tx, ty, Scale, Scale, (UINT8*)NewImage.GetPixelPtr(0,0), iWidth, iHeight, iWidth*4);
nsvgDeleteRasterizer(rast);
*Image = NewImage; //copy array
return EFI_SUCCESS;
}
EFI_STATUS XTheme::ParseSVGXTheme(UINT8* buffer, UINTN Size)
{
EFI_STATUS Status;
Icons.setEmpty();
displayFreeMemory("XTheme::ParseSVGXTheme begin"_XS8);
#if defined(JIEF_DEBUG) && defined(NANOSVG_MEMORY_ALLOCATION_TRACE)
if ( nsvg__nbDanglingPtr() > 0 ) {
DBG("There is already dangling ptr. nano svg memory leak test not done\n");
}else{
char* buffer2 = (char*)malloc(Size);
memcpy(buffer2, buffer, Size);
nvsg__memoryallocation_verbose = false;
NSVGparser* p = nsvgParse(buffer2, 72, 1.f); //the buffer will be modified, it is how nanosvg works
nsvg__deleteParser(p);
if ( nsvg__nbDanglingPtr() > 0 ) {
nsvg__outputDanglingPtr();
nvsg__memoryallocation_verbose = true;
#if 1
// Do it a second time, to display all allocations and to be able to step in with debugger
memcpy(buffer2, buffer, Size);
p = nsvgParse(buffer2, 72, 1.f); //the buffer will be modified, it is how nanosvg works
nsvg__deleteParser(p);
nsvg__outputDanglingPtr();
#endif
}else{
nvsg__memoryallocation_verbose = false; // be sure that nvsg__memoryallocation_verbose is false, as it seems there is no memory leaks
}
}
#else
(void)Size;
#endif
// --- Parse theme.svg --- low case
NSVGparser* SVGParser = nsvgParse((CHAR8*)buffer, 72, 1.f); //the buffer will be modified, it is how nanosvg works// Jief : NEVER cast const to not const. Just change the parameter to not const !!! Nothing better to deceive.
NSVGimage *SVGimage = SVGParser->image;
if (!SVGimage) {
// DBG("Theme not parsed!\n");
return EFI_NOT_STARTED;
}
// --- Get scale as theme design height vs screen height
// must be svg view-box. This is Design Width and Heigth
float vbx = SVGParser->viewWidth;
float vby = SVGParser->viewHeight;
// DBG("Theme view-bounds: w=%f h=%f units=px\n", vbx, vby); //Theme view-bounds: w=1600.000000 h=900.000000 units=px
if (vby > 1.0f) {
SVGimage->height = vby;
} else {
SVGimage->height = 768.f; //default height
}
float ScaleF = UGAHeight / SVGimage->height;
// DBG("using scale %f\n", ScaleF); // using scale 0.666667
Scale = ScaleF;
CentreShift = (vbx * Scale - (float)UGAWidth) * 0.5f;
Background = XImage(UGAWidth, UGAHeight);
if (!BigBack.isEmpty()) {
BigBack.setEmpty();
}
Status = EFI_NOT_FOUND;
if (!Daylight) {
Status = ParseSVGXIcon(SVGParser, BUILTIN_ICON_BACKGROUND, "Background_night"_XS8, &BigBack);
}
if (EFI_ERROR(Status)) {
Status = ParseSVGXIcon(SVGParser, BUILTIN_ICON_BACKGROUND, "Background"_XS8, &BigBack);
}
// DBG(" Background parsed [%lld, %lld]\n", BigBack.GetWidth(), BigBack.GetHeight()); //Background parsed [1067, 133]
// --- Make Banner
Banner.setEmpty(); //for the case of theme switch
Status = EFI_NOT_FOUND;
if (!Daylight) {
Status = ParseSVGXIcon(SVGParser, BUILTIN_ICON_BANNER, "Banner_night"_XS8, &Banner);
}
if (EFI_ERROR(Status)) {
Status = ParseSVGXIcon(SVGParser, BUILTIN_ICON_BANNER, "Banner"_XS8, &Banner);
}
// DBG("Banner parsed\n");
BanHeight = (int)(Banner.GetHeight() * Scale + 1.f);
// DBG(" parsed banner->width=%lld height=%lld\n", Banner.GetWidth(), BanHeight); //parsed banner->width=467 height=89
// --- Make other icons
for (INTN i = BUILTIN_ICON_FUNC_ABOUT; i <= BUILTIN_CHECKBOX_CHECKED; ++i) {
if (i == BUILTIN_ICON_BANNER) { //exclude "logo" as it done as Banner
continue;
}
XIcon* NewIcon = new XIcon(i, false); //initialize without embedded
Status = ParseSVGXIcon(SVGParser, i, NewIcon->Name, &NewIcon->Image);
// DBG("parse %s status %s\n", NewIcon->Name.c_str(), efiStrError(Status));
NewIcon->Native = !EFI_ERROR(Status);
if (!EFI_ERROR(Status)) {
ParseSVGXIcon(SVGParser, i, NewIcon->Name + "_night"_XS8, &NewIcon->ImageNight);
}
// DBG("parse night %s status %s\n", NewIcon->Name.c_str(), efiStrError(Status));
Icons.AddReference(NewIcon, true);
if (EFI_ERROR(Status)) {
if (i >= BUILTIN_ICON_VOL_INTERNAL_HFS && i <= BUILTIN_ICON_VOL_INTERNAL_REC) {
// call to GetIconAlt will get alternate/embedded into Icon if missing
GetIconAlt(i, BUILTIN_ICON_VOL_INTERNAL);
} else if (i == BUILTIN_SELECTION_BIG) {
GetIconAlt(i, BUILTIN_SELECTION_SMALL);
}
}
}
// --- Make other OSes
for (INTN i = ICON_OTHER_OS; i < IconsNamesSize; ++i) {
if (AsciiStrLen(IconsNames[i]) == 0) break;
XIcon* NewIcon = new XIcon(i, false); //initialize without embedded
Status = ParseSVGXIcon(SVGParser, i, NewIcon->Name, &NewIcon->Image);
// DBG("parse %s i=%lld status %s\n", NewIcon->Name.c_str(), i, efiStrError(Status));
NewIcon->Native = !EFI_ERROR(Status);
if (!EFI_ERROR(Status)) {
ParseSVGXIcon(SVGParser, i, NewIcon->Name + "_night"_XS8, &NewIcon->ImageNight);
}
Icons.AddReference(NewIcon, true);
}
//selection for bootcampstyle
XIcon *NewIcon = new XIcon(BUILTIN_ICON_SELECTION);
Status = ParseSVGXIcon(SVGParser, BUILTIN_ICON_SELECTION, "selection_indicator"_XS8, &NewIcon->Image);
if (!EFI_ERROR(Status)) {
Status = ParseSVGXIcon(SVGParser, BUILTIN_ICON_SELECTION, "selection_indicator_night"_XS8, &NewIcon->ImageNight);
}
Icons.AddReference(NewIcon, true);
//selections
SelectionBackgroundPixel.Red = (SelectionColor >> 24) & 0xFF;
SelectionBackgroundPixel.Green = (SelectionColor >> 16) & 0xFF;
SelectionBackgroundPixel.Blue = (SelectionColor >> 8) & 0xFF;
SelectionBackgroundPixel.Reserved = (SelectionColor >> 0) & 0xFF;
//TODO make SelectionImages to be XIcon
SelectionImages[0] = GetIcon(BUILTIN_SELECTION_BIG).GetBest(!Daylight);
SelectionImages[2] = GetIcon(BUILTIN_SELECTION_SMALL).GetBest(!Daylight);
SelectionImages[4] = GetIcon(BUILTIN_ICON_SELECTION).GetBest(!Daylight);
//buttons
for (INTN i = BUILTIN_RADIO_BUTTON; i <= BUILTIN_CHECKBOX_CHECKED; ++i) {
Buttons[i - BUILTIN_RADIO_BUTTON] = GetIcon(i).GetBest(!Daylight);
}
TypeSVG = true;
ThemeDesignHeight = (int)SVGimage->height;
ThemeDesignWidth = (int)SVGimage->width;
if (SelectionOnTop) {
row0TileSize = (INTN)(144.f * Scale);
row1TileSize = (INTN)(64.f * Scale);
MainEntriesSize = (INTN)(128.f * Scale);
}
// DBG("parsing svg theme finished\n");
// It looks like the fonts are self-contained. So we can just keep fontsDB pointer and copy textfaces and delete the parser.
// I'm not sure if font are self contained with all theme. To avoid deleting, just comment out the next line.
// SVGParser will still be deleted at XTheme dtor. So it's not a memory leak.
fontsDB = SVGParser->fontsDB;
for (size_t i = 0; i < sizeof(textFace)/sizeof(textFace[0]); i++) {
textFace[i] = SVGParser->textFace[i];
}
SVGParser->fontsDB = NULL; // To avoid nsvg__deleteParser to delete it;
nsvg__deleteParser(SVGParser); // comment out this line and the next to keep the parser memory, in case of doubt that font are dependent.
SVGParser = NULL;
displayFreeMemory("XTheme::ParseSVGXTheme end"_XS8);
return EFI_SUCCESS;
}
// 2023-11 This is currently never called.
EFI_STATUS XTheme::LoadSvgFrame(NSVGparser* SVGParser, INTN i, OUT XImage* XFrame)
{
EFI_STATUS Status = EFI_NOT_FOUND;
XString8 XFrameName = S8Printf("frame_%04lld", i+1);
Status = ParseSVGXIcon(SVGParser, BUILTIN_ICON_ANIME, XFrameName, XFrame); //svg anime will be full redesigned
if (EFI_ERROR(Status)) {
DBG("frame '%s' not loaded, status=%s\n", XFrameName.c_str(), efiStrError(Status));
}
return Status;
}
// it is not draw, it is render and mainly used in egRenderText
// which is used in icns.cpp as an icon replacement if no image found, looks like not used
// in menu.cpp 3 places
//textType = 0-help 1-message 2-menu 3-test
//return text width in pixels
//it is not theme member!
INTN renderSVGtext(XImage* TextBufferXY_ptr, INTN posX, INTN posY, const textFaces& textFace, const XStringW& string, UINTN Cursor)
{
XImage& TextBufferXY = *TextBufferXY_ptr;
INTN Width;
NSVGparser* p;
NSVGrasterizer* rast;
if (!textFace.valid) {
DBG("invalid fontface!\n");
return 0;
}
NSVGfont* fontSVG = textFace.font;
UINT32 color = textFace.color;
INTN Height = (INTN)(textFace.size * ThemeX->Scale);
float Scale, sy;
float x, y;
if (!fontSVG) {
DBG("no font for renderSVGtext\n");
return 0;
}
p = nsvg__createParser();
if (!p) {
return 0;
}
NSVGtext* text = (NSVGtext*)nsvg__alloczero(sizeof(NSVGtext), "renderSVGtext"_XS8); // use nsvg__alloczero method so it won't panic when it's freed.
if (!text) {
return 0;
}
text->font = fontSVG;
text->fontColor = color;
text->fontSize = (float)Height;
nsvg__xformIdentity(text->xform);
p->text = text;
Width = TextBufferXY.GetWidth();
if ( fontSVG->unitsPerEm < 1.f ) {
fontSVG->unitsPerEm = 1000.f;
}
float fH = fontSVG->bbox[3] - fontSVG->bbox[1]; //1250
if (fH == 0.f) {
DBG("wrong font: %f\n", fontSVG->unitsPerEm);
DumpFloat2("Font bbox", fontSVG->bbox, 4);
fH = (fontSVG->unitsPerEm > 1.f) ? fontSVG->unitsPerEm : 1000.0f; //1000
}
sy = (float)Height / fH; //(float)fontSVG->unitsPerEm; // 260./1250.
Scale = sy;
x = (float)posX; //0.f;
y = (float)posY + fontSVG->bbox[1] * Scale;
p->isText = true;
size_t len = string.length();
for (size_t i=0; i < len; i++) {
CHAR16 letter = string.char16At(i);
if (!letter) {
break;
}
// DBG("add letter 0x%X\n", letter);
if (i == Cursor) {
addLetter(p, 0x5F, x, y, sy, color);
}
x = addLetter(p, letter, x, y, sy, color);
} //end of string
p->image->realBounds[0] = fontSVG->bbox[0] * Scale;
p->image->realBounds[1] = fontSVG->bbox[1] * Scale;
p->image->realBounds[2] = fontSVG->bbox[2] * Scale + x; //last bound
p->image->realBounds[3] = fontSVG->bbox[3] * Scale;
rast = nsvgCreateRasterizer();
nsvgRasterize(rast, p->image, 0, 0, 1.f, 1.f, (UINT8*)TextBufferXY.GetPixelPtr(0,0),
(int)TextBufferXY.GetWidth(), (int)TextBufferXY.GetHeight(), (int)(Width*4));
float RealWidth = p->image->realBounds[2] - p->image->realBounds[0];
nsvgDeleteRasterizer(rast);
nsvg__deleteParser(p); // this deletes p->text;
// nsvgDelete(p->image);
// TODO delete parser p and p->text?
return (INTN)RealWidth; //x;
}
INTN renderSVGtext(XImage* TextBufferXY_ptr, INTN posX, INTN posY, INTN textType, const XStringW& string, UINTN Cursor)
{
if (!ThemeX->getTextFace(textType).valid) {
for (decltype(textType) i=0; i<4; i++) {
if (ThemeX->getTextFace(i).valid) {
textType = i;
break;
}
}
}
if (!ThemeX->getTextFace(textType).valid) {
DBG("valid fontface not found!\n");
return 0;
}
return renderSVGtext(TextBufferXY_ptr, posX, posY, ThemeX->getTextFace(textType), string, Cursor);
}
void testSVG()
{
do {
EFI_STATUS Status;
UINT8 *FileData = NULL;
UINTN FileDataLength = 0;
INTN Width = 192, Height = 192;
#if TEST_MATH
//Test mathematique
//#define fabsf(x) ((x >= 0.0f)?x:(-x))
#define pr(x) (int)fabsf(x), (int)fabsf((x - (int)x) * 1000000.0f)
int i;
float x, y1, y2;
// CHAR8 Str[128];
DBG("Test float: -%d.%06d\n", pr(-0.7612f));
for (i=0; i<15; i++) {
x=(PI)/30.0f*i;
y1=SinF(x);
y2=CosF(x);
DBG("x=%d: %d.%06d ", i*6, pr(x));
DBG(" sinx=%c%d.%06d", (y1<0)?'-':' ', pr(y1));
DBG(" cosx=%c%d.%06d\n", (y2<0)?'-':' ',pr(y2));
y1 = Atan2F(y1, y2);
DBG(" atan2x=%c%d.%06d", (y1<0)?'-':' ',pr(y1));
y1 = AcosF(y2);
DBG(" acos=%c%d.%06d", (y1<0)?'-':' ',pr(y1));
y1 = SqrtF(x);
DBG(" sqrt=%d.%06d", pr(y1));
y1 = CeilF(x);
DBG(" ceil=%c%d.%06d\n", (y1<0)?'-':' ',pr(y1));
}
#undef pr
#endif
NSVGparser* p;
#if TEST_DITHER
{
EG_IMAGE *RndImage = egCreateImage(256, 256, false);
INTN i,j;
EG_PIXEL pixel = WhitePixel;
for (i=0; i<256; i++) {
for (j=0; j<256; j++) {
pixel.b = 0x40 + (dither((float)j / 32.0f, 1) * 8);
pixel.r = 0x0;
pixel.g = 0x0;
// if (i==1) {
// DBG("r=%X g=%X\n", pixel.r, pixel.g);
// }
RndImage->PixelData[i * 256 + j] = pixel;
}
}
BltImageAlpha(RndImage,
20,
20,
&MenuBackgroundPixel,
16);
}
#endif
#if TEST_SVG_IMAGE
NSVGrasterizer* rast = nsvgCreateRasterizer();
// EG_IMAGE *NewImage;
NSVGimage *SVGimage;
float Scale, ScaleX, ScaleY;
// load file
Status = egLoadFile(&self.getSelfVolumeRootDir(), L"Sample.svg", &FileData, &FileDataLength);
if (!EFI_ERROR(Status)) {
//Parse XML to vector data
p = nsvgParse((CHAR8*)FileData, 72, 1.f);
SVGimage = p->image;
DBG("Test image width=%d heigth=%d\n", (int)(SVGimage->width), (int)(SVGimage->height));
// Rasterize
XImage NewImage(Width, Height);
if (SVGimage->width <= 0) SVGimage->width = (float)Width;
if (SVGimage->height <= 0) SVGimage->height = (float)Height;
ScaleX = Width / SVGimage->width;
ScaleY = Height / SVGimage->height;
Scale = (ScaleX > ScaleY)?ScaleY:ScaleX;
float tx = 0; //-SVGimage->realBounds[0] * Scale;
float ty = 0; //-SVGimage->realBounds[1] * Scale;
DBG("timing rasterize start tx=%f ty=%f\n", tx, ty); //the aim is measure duration
nsvgRasterize(rast, SVGimage, tx,ty,Scale,Scale, (UINT8*)NewImage.GetPixelPtr(0,0), (int)Width, (int)Height, (int)Width*4);
DBG("timing rasterize end\n");
NewImage.Draw((UGAWidth - Width) / 2,
(UGAHeight - Height) / 2);
FreePool(FileData);
FileData = NULL;
//
// nsvg__deleteParser(p);
nsvgDeleteRasterizer(rast);
}
#endif
//Test text
Height = 80;
Width = UGAWidth-200;
// DBG("create test textbuffer\n");
XImage TextBufferXY(Width, Height);
Status = egLoadFile(&self.getSelfVolumeRootDir(), L"Font.svg", &FileData, &FileDataLength);
DBG("test Font.svg loaded status=%s\n", efiStrError(Status));
if (!EFI_ERROR(Status)) {
p = nsvgParse((CHAR8*)FileData, 72, 1.f);
if (!p) {
DBG("font not parsed\n");
break;
}
textFaces textFace;
textFace.font = p->currentFont;
textFace.color = NSVG_RGBA(0x80, 0xFF, 0, 255);
textFace.size = Height;
textFace.valid = true;
// DBG("font parsed family=%s\n", p->font->fontFamily);
FreePool(FileData);
// Scale = Height / fontSVG->unitsPerEm;
renderSVGtext(&TextBufferXY, 0, 0, 3, XStringW().takeValueFrom("Clover Кловер"), 1);
// DBG("text ready to blit\n");
TextBufferXY.Draw((UGAWidth - Width) / 2,
(UGAHeight - Height) / 2);
// nsvg__deleteParser(p);
// DBG("draw finished\n");
}
} while (0);
}