CloverBootloader/rEFIt_UEFI/libeg/VectorGraphics.cpp
2024-01-08 11:27:38 +01:00

567 lines
19 KiB
C++

/*
* 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 LString8 IconsNames[]; -> Include XIcon.h instead if duplicating declaration.
//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);
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 ( nsvg__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 = nsvg__createRasterizer();
nsvgRasterize(rast, SVGimage, bounds, IconNameX.c_str(), tx, ty, Scale, Scale, (UINT8*)NewImage.GetPixelPtr(0,0), iWidth, iHeight, iWidth*4);
nsvg__deleteRasterizer(rast);
*Image = NewImage; //copy array
return EFI_SUCCESS;
}
EFI_STATUS XTheme::ParseSVGXTheme(UINT8* buffer, UINTN Size)
{
EFI_STATUS Status;
Icons.setEmpty();
#ifdef JIEF_DEBUG
displayFreeMemory("XTheme::ParseSVGXTheme begin"_XS8);
#endif
#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{
apd<char*> buffer2 = (char*)malloc(Size);
memcpy(buffer2, buffer, Size);
nvsg__memoryallocation_verbose = false;
NSVGparser* p = nsvg__parse(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 = nsvg__parse(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 = nsvg__parse((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 ( IconsNames[i].isEmpty() ) 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;
#ifdef JIEF_DEBUG
displayFreeMemory("XTheme::ParseSVGXTheme end"_XS8);
#endif
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);
nsvg__dumpFloat("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) {
nsvg__addLetter(p, 0x5F, x, y, sy, color);
}
x = nsvg__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 = nsvg__createRasterizer();
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];
nsvg__deleteRasterizer(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 = nsvg__createRasterizer();
// 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 = nsvg__parse((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);
nsvg__deleteRasterizer(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 = nsvg__parse((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);
}