]> code.delx.au - refind/blobdiff - libeg/image.c
New image-scaling code; used for icons and (optionally) for scaling
[refind] / libeg / image.c
index 4cf282e1f0a6b9867ab793cd07cb0559c1095c93..530589a692d774294422f3eef2841e60fce75a15 100644 (file)
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
+/*
+ * Modifications copyright (c) 2012-2013 Roderick W. Smith
+ * 
+ * Modifications distributed under the terms of the GNU General Public
+ * License (GPL) version 3 (GPLv3), a copy of which must be distributed
+ * with this source code or binaries made from it.
+ * 
+ */
 
 #include "libegint.h"
 #include "../refind/global.h"
@@ -85,9 +93,10 @@ EG_IMAGE * egCreateFilledImage(IN UINTN Width, IN UINTN Height, IN BOOLEAN HasAl
 
 EG_IMAGE * egCopyImage(IN EG_IMAGE *Image)
 {
-    EG_IMAGE        *NewImage;
+    EG_IMAGE        *NewImage = NULL;
 
-    NewImage = egCreateImage(Image->Width, Image->Height, Image->HasAlpha);
+    if (Image != NULL)
+       NewImage = egCreateImage(Image->Width, Image->Height, Image->HasAlpha);
     if (NewImage == NULL)
         return NULL;
 
@@ -116,6 +125,67 @@ EG_IMAGE * egCropImage(IN EG_IMAGE *Image, IN UINTN StartX, IN UINTN StartY, IN
    return NewImage;
 } // EG_IMAGE * egCropImage()
 
+// The following function implements a bilinear image scaling algorithm, based on
+// code presented at http://tech-algorithm.com/articles/bilinear-image-scaling/.
+// Resize an image; returns pointer to resized image if successful, NULL otherwise.
+// Calling function is responsible for freeing allocated memory.
+EG_IMAGE * egScaleImage(EG_IMAGE *Image, UINTN NewWidth, UINTN NewHeight) {
+   EG_IMAGE *NewImage = NULL;
+   EG_PIXEL a, b, c, d;
+   UINTN x, y, Index ;
+   UINTN i, j;
+   UINTN Offset = 0;
+   float x_ratio, y_ratio, x_diff, y_diff;
+
+   if ((Image == NULL) || (Image->Height == 0) || (Image->Width == 0) || (NewWidth == 0) || (NewHeight == 0))
+      return NULL;
+
+   if ((Image->Width == NewWidth) && (Image->Height == NewHeight))
+      return (egCopyImage(Image));
+
+   NewImage = egCreateImage(NewWidth, NewHeight, Image->HasAlpha);
+   if (NewImage == NULL)
+      return NULL;
+
+   x_ratio = ((float)(Image->Width-1))/NewWidth ;
+   y_ratio = ((float)(Image->Height-1))/NewHeight ;
+
+   for (i = 0; i < NewHeight; i++) {
+      for (j = 0; j < NewWidth; j++) {
+         x = (UINTN)(x_ratio * j) ;
+         y = (UINTN)(y_ratio * i) ;
+         x_diff = (x_ratio * j) - x ;
+         y_diff = (y_ratio * i) - y ;
+         Index = ((y * Image->Width) + x) ;
+         a = Image->PixelData[Index] ;
+         b = Image->PixelData[Index + 1] ;
+         c = Image->PixelData[Index + Image->Width] ;
+         d = Image->PixelData[Index + Image->Width + 1] ;
+
+         // blue element
+         // Yb = Ab(1-Image->Width)(1-Image->Height) + Bb(Image->Width)(1-Image->Height) + Cb(Image->Height)(1-Image->Width) + Db(wh)
+         NewImage->PixelData[Offset].b = (a.b)*(1-x_diff)*(1-y_diff) + (b.b)*(x_diff)*(1-y_diff) +
+                                         (c.b)*(y_diff)*(1-x_diff)   + (d.b)*(x_diff*y_diff);
+
+         // green element
+         // Yg = Ag(1-Image->Width)(1-Image->Height) + Bg(Image->Width)(1-Image->Height) + Cg(Image->Height)(1-Image->Width) + Dg(wh)
+         NewImage->PixelData[Offset].g = (a.g)*(1-x_diff)*(1-y_diff) + (b.g)*(x_diff)*(1-y_diff) +
+                                         (c.g)*(y_diff)*(1-x_diff)   + (d.g)*(x_diff*y_diff);
+
+         // red element
+         // Yr = Ar(1-Image->Width)(1-Image->Height) + Br(Image->Width)(1-Image->Height) + Cr(Image->Height)(1-Image->Width) + Dr(wh)
+         NewImage->PixelData[Offset].r = (a.r)*(1-x_diff)*(1-y_diff) + (b.r)*(x_diff)*(1-y_diff) +
+                                         (c.r)*(y_diff)*(1-x_diff)   + (d.r)*(x_diff*y_diff);
+
+         // alpha element
+         NewImage->PixelData[Offset++].a = (a.a)*(1-x_diff)*(1-y_diff) + (b.a)*(x_diff)*(1-y_diff) +
+                                           (c.a)*(y_diff)*(1-x_diff)   + (d.a)*(x_diff*y_diff);
+
+      } // for (j...)
+   } // for (i...)
+   return NewImage;
+} // EG_IMAGE * egScaleImage()
+
 VOID egFreeImage(IN EG_IMAGE *Image)
 {
     if (Image != NULL) {
@@ -129,8 +199,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;
@@ -175,7 +244,7 @@ EFI_STATUS egLoadFile(IN EFI_FILE* BaseDir, IN CHAR16 *FileName,
 
 static EFI_GUID ESPGuid = { 0xc12a7328, 0xf81f, 0x11d2, { 0xba, 0x4b, 0x00, 0xa0, 0xc9, 0x3e, 0xc9, 0x3b } };
 
-static EFI_STATUS egFindESP(OUT EFI_FILE_HANDLE *RootDir)
+EFI_STATUS egFindESP(OUT EFI_FILE_HANDLE *RootDir)
 {
     EFI_STATUS          Status;
     UINTN               HandleCount = 0;
@@ -205,7 +274,7 @@ EFI_STATUS egSaveFile(IN EFI_FILE* BaseDir OPTIONAL, IN CHAR16 *FileName,
     }
 
     Status = refit_call5_wrapper(BaseDir->Open, BaseDir, &FileHandle, FileName,
-                           EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE, 0);
+                                 EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE, 0);
     if (EFI_ERROR(Status))
         return Status;
 
@@ -220,36 +289,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 = egDecodeBMP(FileData, FileDataLength, IconSize, WantAlpha);
-   } else if ((StriCmp(Format, L"ICNS") == 0) || (StriCmp(Format, L"icns") == 0)) {
-      NewImage = egDecodeICNS(FileData, FileDataLength, IconSize, WantAlpha);
-   } else if ((StriCmp(Format, L"PNG") == 0) || (StriCmp(Format, L"png") == 0)) {
+   NewImage = egDecodeICNS(FileData, FileDataLength, IconSize, WantAlpha);
+   if (NewImage == NULL)
       NewImage = egDecodePNG(FileData, FileDataLength, IconSize, WantAlpha);
-   } // if/else
+   if (NewImage == NULL)
+      NewImage = egDecodeBMP(FileData, FileDataLength, IconSize, WantAlpha);
 
    return NewImage;
 }
@@ -270,56 +321,84 @@ 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;
+    EG_IMAGE        *Image, *NewImage;
 
     if (BaseDir == NULL || Path == NULL)
         return NULL;
 
     // 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);
+    Image = 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;
+    if ((Image->Width != IconSize) || (Image->Height != IconSize)) {
+       NewImage = egScaleImage(Image, IconSize, IconSize);
+       if (!NewImage)
+          Print(L"Warning: Unable to scale icon of the wrong size from '%s'\n", Path);
+       MyFreePool(Image);
+       Image = NewImage;
     }
 
-    return NewImage;
+    return Image;
 } // 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 of any type from the specified subdirectory using the specified
+// base name. All directory references are relative to BaseDir. For instance, if
+// SubdirName is "myicons" and BaseName is "os_linux", this function will return
+// an image based on "myicons/os_linux.icns" or "myicons/os_linux.png", in that
+// order of preference. Returns NULL if no such file is a valid icon file.
+EG_IMAGE * egLoadIconAnyType(IN EFI_FILE *BaseDir, IN CHAR16 *SubdirName, IN CHAR16 *BaseName, IN UINTN IconSize) {
+   EG_IMAGE *Image = NULL;
+   CHAR16 *Extension;
+   CHAR16 FileName[256];
+   UINTN i = 0;
+
+   while (((Extension = FindCommaDelimited(ICON_EXTENSIONS, i++)) != NULL) && (Image == NULL)) {
+      SPrint(FileName, 255, L"%s\\%s.%s", SubdirName, BaseName, Extension);
+      Image = egLoadIcon(BaseDir, FileName, IconSize);
+      MyFreePool(Extension);
+   } // while()
+
+   return Image;
+} // EG_IMAGE *egLoadIconAnyType()
+
+// 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) {
+   EG_IMAGE *Image = NULL;
+
+   if (GlobalConfig.IconsDir != NULL) {
+      Image = egLoadIconAnyType(SelfDir, GlobalConfig.IconsDir, BaseName, IconSize);
+   }
+
+   if (Image == NULL) {
+      Image = egLoadIconAnyType(SelfDir, DEFAULT_ICONS_DIR, BaseName, IconSize);
+   }
+
+   return Image;
+} // EG_IMAGE * egFindIcon()
 
 EG_IMAGE * egPrepareEmbeddedImage(IN EG_EMBEDDED_IMAGE *EmbeddedImage, IN BOOLEAN WantAlpha)
 {
@@ -528,11 +607,6 @@ VOID egComposeImage(IN OUT EG_IMAGE *CompImage, IN EG_IMAGE *TopImage, IN UINTN
 
     // compose
     if (CompWidth > 0) {
-//         if (CompImage->HasAlpha) {
-//             CompImage->HasAlpha = FALSE;
-//             egSetPlane(PLPTR(CompImage, a), 0, CompImage->Width * CompImage->Height);
-//         }
-
         if (TopImage->HasAlpha) {
             egRawCompose(CompImage->PixelData + PosY * CompImage->Width + PosX, TopImage->PixelData,
                          CompWidth, CompHeight, CompImage->Width, TopImage->Width);
@@ -541,7 +615,7 @@ VOID egComposeImage(IN OUT EG_IMAGE *CompImage, IN EG_IMAGE *TopImage, IN UINTN
                       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)
 {
@@ -557,6 +631,7 @@ EG_IMAGE * egEnsureImageSize(IN EG_IMAGE *Image, IN UINTN Width, IN UINTN Height
         egFreeImage(Image);
         return NULL;
     }
+    Image->HasAlpha = FALSE;
     egComposeImage(NewImage, Image, 0, 0);
     egFreeImage(Image);