]> code.delx.au - refind/blobdiff - libeg/image.c
Fixed bug: ICNS file in main icons directory would override PNG file in
[refind] / libeg / image.c
index 28d2472a40d0334c92d7732f2449b7b281d152d2..5fc915dfc81a0b5e4364911780493459c01a9051 100644 (file)
@@ -39,6 +39,7 @@
 #include "../refind/lib.h"
 #include "../refind/screen.h"
 #include "../include/refit_call_wrapper.h"
+#include "lodepng.h"
 
 #define MAX_FILE_SIZE (1024*1024*1024)
 
@@ -94,6 +95,27 @@ EG_IMAGE * egCopyImage(IN EG_IMAGE *Image)
     return NewImage;
 }
 
+// Returns a smaller image composed of the specified crop area from the larger area.
+// If the specified area is larger than is in the original, returns NULL.
+EG_IMAGE * egCropImage(IN EG_IMAGE *Image, IN UINTN StartX, IN UINTN StartY, IN UINTN Width, IN UINTN Height) {
+   EG_IMAGE *NewImage = NULL;
+   UINTN x, y;
+
+   if (((StartX + Width) > Image->Width) || ((StartY + Height) > Image->Height))
+      return NULL;
+
+   NewImage = egCreateImage(Width, Height, Image->HasAlpha);
+   if (NewImage == NULL)
+      return NULL;
+
+   for (y = 0; y < Height; y++) {
+      for (x = 0; x < Width; x++) {
+         NewImage->PixelData[y * NewImage->Width + x] = Image->PixelData[(y + StartY) * Image->Width + x + StartX];
+      }
+   }
+   return NewImage;
+} // EG_IMAGE * egCropImage()
+
 VOID egFreeImage(IN EG_IMAGE *Image)
 {
     if (Image != NULL) {
@@ -107,8 +129,7 @@ VOID egFreeImage(IN EG_IMAGE *Image)
 // Basic file operations
 //
 
-EFI_STATUS egLoadFile(IN EFI_FILE* BaseDir, IN CHAR16 *FileName,
-                      OUT UINT8 **FileData, OUT UINTN *FileDataLength)
+EFI_STATUS egLoadFile(IN EFI_FILE* BaseDir, IN CHAR16 *FileName, OUT UINT8 **FileData, OUT UINTN *FileDataLength)
 {
     EFI_STATUS          Status;
     EFI_FILE_HANDLE     FileHandle;
@@ -198,34 +219,18 @@ EFI_STATUS egSaveFile(IN EFI_FILE* BaseDir OPTIONAL, IN CHAR16 *FileName,
 // Loading images from files and embedded data
 //
 
-static CHAR16 * egFindExtension(IN CHAR16 *FileName)
-{
-    UINTN i;
-
-    for (i = StrLen(FileName); i >= 0; i--) {
-        if (FileName[i] == '.')
-            return FileName + i + 1;
-        if (FileName[i] == '/' || FileName[i] == '\\')
-            break;
-    }
-    return FileName + StrLen(FileName);
-}
-
-static EG_IMAGE * egDecodeAny(IN UINT8 *FileData, IN UINTN FileDataLength,
-                              IN CHAR16 *Format, IN UINTN IconSize, IN BOOLEAN WantAlpha)
+// Decode the specified image data. The IconSize parameter is relevant only
+// for ICNS, for which it selects which ICNS sub-image is decoded.
+// Returns a pointer to the resulting EG_IMAGE or NULL if decoding failed.
+static EG_IMAGE * egDecodeAny(IN UINT8 *FileData, IN UINTN FileDataLength, IN UINTN IconSize, IN BOOLEAN WantAlpha)
 {
    EG_IMAGE        *NewImage = NULL;
 
-   // Note: The UEFI implementation in Gigabyte's Hybrid EFI is buggy and does
-   // a case-sensitive comparison in StriCmp rather than the case-insensitive
-   // comparison that the spec says should be done. As a workaround, we repeat
-   // the comparison twice here.
-   // dispatch by extension
-   if ((StriCmp(Format, L"BMP") == 0) || (StriCmp(Format, L"bmp") == 0)) {
+   NewImage = egDecodeICNS(FileData, FileDataLength, IconSize, WantAlpha);
+   if (NewImage == NULL)
+      NewImage = egDecodePNG(FileData, FileDataLength, IconSize, WantAlpha);
+   if (NewImage == NULL)
       NewImage = egDecodeBMP(FileData, FileDataLength, IconSize, WantAlpha);
-   } else if ((StriCmp(Format, L"ICNS") == 0) || (StriCmp(Format, L"icns") == 0)) {
-      NewImage = egDecodeICNS(FileData, FileDataLength, IconSize, WantAlpha);
-   } // if/else
 
    return NewImage;
 }
@@ -246,25 +251,19 @@ EG_IMAGE * egLoadImage(IN EFI_FILE* BaseDir, IN CHAR16 *FileName, IN BOOLEAN Wan
         return NULL;
 
     // decode it
-    NewImage = egDecodeAny(FileData, FileDataLength, egFindExtension(FileName), 128, WantAlpha);
+    NewImage = egDecodeAny(FileData, FileDataLength, 128, WantAlpha);
     FreePool(FileData);
 
     return NewImage;
 }
 
 // Load an icon from (BaseDir)/Path, extracting the icon of size IconSize x IconSize.
-// If the initial attempt is unsuccessful, try again, replacing the directory
-// component of Path with DEFAULT_ICONS_DIR.
-// Note: The assumption is that BaseDir points to rEFInd's home directory and Path
-// includes one subdirectory level. If this changes in future revisions, it may be
-// necessary to alter the code that tries again with DEFAULT_ICONS_DIR.
 // Returns a pointer to the image data, or NULL if the icon could not be loaded.
 EG_IMAGE * egLoadIcon(IN EFI_FILE* BaseDir, IN CHAR16 *Path, IN UINTN IconSize)
 {
     EFI_STATUS      Status;
     UINT8           *FileData;
     UINTN           FileDataLength;
-    CHAR16          *FileName, FileName2[256];
     EG_IMAGE        *NewImage;
 
     if (BaseDir == NULL || Path == NULL)
@@ -272,25 +271,56 @@ EG_IMAGE * egLoadIcon(IN EFI_FILE* BaseDir, IN CHAR16 *Path, IN UINTN IconSize)
 
     // load file
     Status = egLoadFile(BaseDir, Path, &FileData, &FileDataLength);
-    if (EFI_ERROR(Status)) {
-        FileName = Basename(Path); // Note: FileName is a pointer within Path; DON'T FREE IT!
-        SPrint(FileName2, 255, L"%s\\%s", DEFAULT_ICONS_DIR, FileName);
-        Status = egLoadFile(BaseDir, FileName2, &FileData, &FileDataLength);
-        if (EFI_ERROR(Status))
-           return NULL;
-    }
+    if (EFI_ERROR(Status))
+       return NULL;
 
     // decode it
-    NewImage = egDecodeAny(FileData, FileDataLength, egFindExtension(Path), IconSize, TRUE);
+    NewImage = egDecodeAny(FileData, FileDataLength, IconSize, TRUE);
     FreePool(FileData);
+    if ((NewImage->Width != IconSize) || (NewImage->Height != IconSize)) {
+       Print(L"Warning: Attempt to load icon of the wrong size from '%s'\n", Path);
+       MyFreePool(NewImage);
+       NewImage = NULL;
+    }
 
     return NewImage;
 } // EG_IMAGE *egLoadIcon()
 
-EG_IMAGE * egDecodeImage(IN UINT8 *FileData, IN UINTN FileDataLength, IN CHAR16 *Format, IN BOOLEAN WantAlpha)
-{
-    return egDecodeAny(FileData, FileDataLength, Format, 128, WantAlpha);
-}
+// Returns an icon with any extension in ICON_EXTENSIONS from either the directory
+// specified by GlobalConfig.IconsDir or DEFAULT_ICONS_DIR. The input BaseName
+// should be the icon name without an extension. For instance, if BaseName is
+// os_linux, GlobalConfig.IconsDir is myicons, DEFAULT_ICONS_DIR is icons, and
+// ICON_EXTENSIONS is "icns,png", this function will return myicons/os_linux.icns,
+// myicons/os_linux.png, icons/os_linux.icns, or icons/os_linux.png, in that
+// order of preference. Returns NULL if no such icon can be found. All file
+// references are relative to SelfDir.
+EG_IMAGE * egFindIcon(IN CHAR16 *BaseName, IN UINTN IconSize) {
+   CHAR16 *LoadDir, *Extension;
+   CHAR16 FileName[256];
+   UINTN i;
+   EG_IMAGE *Image = NULL;
+
+   if (GlobalConfig.IconsDir != NULL)
+      LoadDir = GlobalConfig.IconsDir;
+   else
+      LoadDir = StrDuplicate(DEFAULT_ICONS_DIR);
+
+   while ((LoadDir != NULL) && (Image == NULL)) {
+      i = 0;
+      while (((Extension = FindCommaDelimited(ICON_EXTENSIONS, i++)) != NULL) && (Image == NULL)) {
+         SPrint(FileName, 255, L"%s\\%s.%s", LoadDir, BaseName, Extension);
+         Image = egLoadIcon(SelfDir, FileName, IconSize);
+         MyFreePool(Extension);
+      } // while()
+      if (LoadDir == GlobalConfig.IconsDir) {
+         LoadDir = StrDuplicate(DEFAULT_ICONS_DIR);
+      } else {
+         MyFreePool(LoadDir);
+         LoadDir = NULL;
+      } // if/else
+   } // while()
+   return Image;
+} // EG_IMAGE * egFindIcon()
 
 EG_IMAGE * egPrepareEmbeddedImage(IN EG_EMBEDDED_IMAGE *EmbeddedImage, IN BOOLEAN WantAlpha)
 {
@@ -399,11 +429,11 @@ VOID egFillImage(IN OUT EG_IMAGE *CompImage, IN EG_PIXEL *Color)
     UINTN       i;
     EG_PIXEL    FillColor;
     EG_PIXEL    *PixelPtr;
-    
+
     FillColor = *Color;
     if (!CompImage->HasAlpha)
         FillColor.a = 0;
-    
+
     PixelPtr = CompImage->PixelData;
     for (i = 0; i < CompImage->Width * CompImage->Height; i++, PixelPtr++)
         *PixelPtr = FillColor;
@@ -418,14 +448,14 @@ VOID egFillImageArea(IN OUT EG_IMAGE *CompImage,
     EG_PIXEL    FillColor;
     EG_PIXEL    *PixelPtr;
     EG_PIXEL    *PixelBasePtr;
-    
+
     egRestrictImageArea(CompImage, AreaPosX, AreaPosY, &AreaWidth, &AreaHeight);
-    
+
     if (AreaWidth > 0) {
         FillColor = *Color;
         if (!CompImage->HasAlpha)
             FillColor.a = 0;
-        
+
         PixelBasePtr = CompImage->PixelData + AreaPosY * CompImage->Width + AreaPosX;
         for (y = 0; y < AreaHeight; y++) {
             PixelPtr = PixelBasePtr;
@@ -442,7 +472,7 @@ VOID egRawCopy(IN OUT EG_PIXEL *CompBasePtr, IN EG_PIXEL *TopBasePtr,
 {
     UINTN       x, y;
     EG_PIXEL    *TopPtr, *CompPtr;
-    
+
     for (y = 0; y < Height; y++) {
         TopPtr = TopBasePtr;
         CompPtr = CompBasePtr;
@@ -464,7 +494,7 @@ VOID egRawCompose(IN OUT EG_PIXEL *CompBasePtr, IN EG_PIXEL *TopBasePtr,
     UINTN       Alpha;
     UINTN       RevAlpha;
     UINTN       Temp;
-    
+
     for (y = 0; y < Height; y++) {
         TopPtr = TopBasePtr;
         CompPtr = CompBasePtr;
@@ -492,26 +522,22 @@ VOID egRawCompose(IN OUT EG_PIXEL *CompBasePtr, IN EG_PIXEL *TopBasePtr,
 VOID egComposeImage(IN OUT EG_IMAGE *CompImage, IN EG_IMAGE *TopImage, IN UINTN PosX, IN UINTN PosY)
 {
     UINTN       CompWidth, CompHeight;
-    
+
     CompWidth  = TopImage->Width;
     CompHeight = TopImage->Height;
     egRestrictImageArea(CompImage, PosX, PosY, &CompWidth, &CompHeight);
-    
+
     // compose
     if (CompWidth > 0) {
-        if (CompImage->HasAlpha) {
-            CompImage->HasAlpha = FALSE;
-            egSetPlane(PLPTR(CompImage, a), 0, CompImage->Width * CompImage->Height);
-        }
-        
-        if (TopImage->HasAlpha)
+        if (TopImage->HasAlpha) {
             egRawCompose(CompImage->PixelData + PosY * CompImage->Width + PosX, TopImage->PixelData,
                          CompWidth, CompHeight, CompImage->Width, TopImage->Width);
-        else
+        } else {
             egRawCopy(CompImage->PixelData + PosY * CompImage->Width + PosX, TopImage->PixelData,
                       CompWidth, CompHeight, CompImage->Width, TopImage->Width);
+        }
     }
-}
+} /* VOID egComposeImage() */
 
 EG_IMAGE * egEnsureImageSize(IN EG_IMAGE *Image, IN UINTN Width, IN UINTN Height, IN EG_PIXEL *Color)
 {
@@ -521,15 +547,16 @@ EG_IMAGE * egEnsureImageSize(IN EG_IMAGE *Image, IN UINTN Width, IN UINTN Height
         return NULL;
     if (Image->Width == Width && Image->Height == Height)
         return Image;
-    
+
     NewImage = egCreateFilledImage(Width, Height, Image->HasAlpha, Color);
     if (NewImage == NULL) {
         egFreeImage(Image);
         return NULL;
     }
+    Image->HasAlpha = FALSE;
     egComposeImage(NewImage, Image, 0, 0);
     egFreeImage(Image);
-    
+
     return NewImage;
 }
 
@@ -540,7 +567,7 @@ EG_IMAGE * egEnsureImageSize(IN EG_IMAGE *Image, IN UINTN Width, IN UINTN Height
 VOID egInsertPlane(IN UINT8 *SrcDataPtr, IN UINT8 *DestPlanePtr, IN UINTN PixelCount)
 {
     UINTN i;
-    
+
     for (i = 0; i < PixelCount; i++) {
         *DestPlanePtr = *SrcDataPtr++;
         DestPlanePtr += 4;
@@ -550,7 +577,7 @@ VOID egInsertPlane(IN UINT8 *SrcDataPtr, IN UINT8 *DestPlanePtr, IN UINTN PixelC
 VOID egSetPlane(IN UINT8 *DestPlanePtr, IN UINT8 Value, IN UINTN PixelCount)
 {
     UINTN i;
-    
+
     for (i = 0; i < PixelCount; i++) {
         *DestPlanePtr = Value;
         DestPlanePtr += 4;
@@ -560,7 +587,7 @@ VOID egSetPlane(IN UINT8 *DestPlanePtr, IN UINT8 Value, IN UINTN PixelCount)
 VOID egCopyPlane(IN UINT8 *SrcPlanePtr, IN UINT8 *DestPlanePtr, IN UINTN PixelCount)
 {
     UINTN i;
-    
+
     for (i = 0; i < PixelCount; i++) {
         *DestPlanePtr = *SrcPlanePtr;
         DestPlanePtr += 4, SrcPlanePtr += 4;