354 lines
10 KiB
C

/** @file
This file is part of OpenCanopy, OpenCore GUI.
Copyright (c) 2018-2019, Download-Fritz. All rights reserved.<BR>
SPDX-License-Identifier: BSD-3-Clause
**/
#include <Uefi.h>
#include <IndustryStandard/AppleIcon.h>
#include <IndustryStandard/AppleDiskLabel.h>
#include <Library/BaseLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/DebugLib.h>
#include <Library/OcCompressionLib.h>
#include <Library/OcPngLib.h>
#include "OpenCanopy.h"
//
// Disk label palette.
//
STATIC
CONST UINT8
gAppleDiskLabelImagePalette[256] = {
[0x00] = 255,
[0xf6] = 238,
[0xf7] = 221,
[0x2a] = 204,
[0xf8] = 187,
[0xf9] = 170,
[0x55] = 153,
[0xfa] = 136,
[0xfb] = 119,
[0x80] = 102,
[0xfc] = 85,
[0xfd] = 68,
[0xab] = 51,
[0xfe] = 34,
[0xff] = 17,
[0xd6] = 0
};
EFI_STATUS
GuiIcnsToImageIcon (
OUT GUI_IMAGE *Image,
IN VOID *IcnsImage,
IN UINT32 IcnsImageSize,
IN UINT8 Scale,
IN UINT32 MatchWidth,
IN UINT32 MatchHeight,
IN BOOLEAN AllowLess
)
{
EFI_STATUS Status;
UINT32 Offset;
UINT32 RecordLength;
UINT32 ImageSize;
UINT32 DecodedBytes;
APPLE_ICNS_RECORD *Record;
APPLE_ICNS_RECORD *RecordIT32;
APPLE_ICNS_RECORD *RecordT8MK;
ASSERT (Scale == 1 || Scale == 2);
//
// We do not need to support 'it32' 128x128 icon format,
// because Finder automatically converts the icons to PNG-based
// when assigning volume icon.
//
if (IcnsImageSize < sizeof (APPLE_ICNS_RECORD)*2) {
return EFI_INVALID_PARAMETER;
}
Record = IcnsImage;
if ((Record->Type != APPLE_ICNS_MAGIC) || (SwapBytes32 (Record->Size) != IcnsImageSize)) {
return EFI_SECURITY_VIOLATION;
}
RecordIT32 = NULL;
RecordT8MK = NULL;
Offset = sizeof (APPLE_ICNS_RECORD);
while (Offset < IcnsImageSize - sizeof (APPLE_ICNS_RECORD)) {
Record = (APPLE_ICNS_RECORD *)((UINT8 *)IcnsImage + Offset);
RecordLength = SwapBytes32 (Record->Size);
//
// 1. Record smaller than its header and 1 32-bit word is invalid.
// 32-bit is required by some entries like IT32 (see below).
// 2. Record overflowing UINT32 is invalid.
// 3. Record larger than file size is invalid.
//
if ( (RecordLength < sizeof (APPLE_ICNS_RECORD) + sizeof (UINT32))
|| OcOverflowAddU32 (Offset, RecordLength, &Offset)
|| (Offset > IcnsImageSize))
{
return EFI_SECURITY_VIOLATION;
}
if ( ((Scale == 1) && (Record->Type == APPLE_ICNS_IC07))
|| ((Scale == 2) && (Record->Type == APPLE_ICNS_IC13)))
{
Status = GuiPngToImage (
Image,
Record->Data,
RecordLength - sizeof (APPLE_ICNS_RECORD),
TRUE
);
if (!EFI_ERROR (Status) && (MatchWidth > 0) && (MatchHeight > 0)) {
if (AllowLess
? ( (Image->Width > MatchWidth * Scale) || (Image->Height > MatchWidth * Scale)
|| (Image->Width == 0) || (Image->Height == 0))
: ((Image->Width != MatchWidth * Scale) || (Image->Height != MatchHeight * Scale)))
{
FreePool (Image->Buffer);
DEBUG ((
DEBUG_INFO,
"OCUI: Expected %dx%d, actual %dx%d, allow less: %d\n",
MatchWidth * Scale,
MatchHeight * Scale,
Image->Width,
Image->Height,
AllowLess
));
Status = EFI_UNSUPPORTED;
}
}
return Status;
}
if (Scale == 1) {
if (Record->Type == APPLE_ICNS_IT32) {
RecordIT32 = Record;
} else if (Record->Type == APPLE_ICNS_T8MK) {
RecordT8MK = Record;
}
if ((RecordT8MK != NULL) && (RecordIT32 != NULL)) {
Image->Width = MatchWidth;
Image->Height = MatchHeight;
ImageSize = (MatchWidth * MatchHeight) * sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL);
Image->Buffer = AllocateZeroPool (ImageSize);
if (Image->Buffer == NULL) {
return EFI_OUT_OF_RESOURCES;
}
//
// We have to add an additional UINT32 for IT32, since it has a reserved field.
//
DecodedBytes = DecompressMaskedRLE24 (
(UINT8 *)Image->Buffer,
ImageSize,
RecordIT32->Data + sizeof (UINT32),
SwapBytes32 (RecordIT32->Size) - sizeof (APPLE_ICNS_RECORD) - sizeof (UINT32),
RecordT8MK->Data,
SwapBytes32 (RecordT8MK->Size) - sizeof (APPLE_ICNS_RECORD),
TRUE
);
if (DecodedBytes != ImageSize) {
FreePool (Image->Buffer);
return EFI_UNSUPPORTED;
}
return EFI_SUCCESS;
}
}
}
return EFI_NOT_FOUND;
}
EFI_STATUS
GuiLabelToImage (
OUT GUI_IMAGE *Image,
IN VOID *RawData,
IN UINT32 DataLength,
IN UINT8 Scale,
IN BOOLEAN Inverted
)
{
APPLE_DISK_LABEL *Label;
UINT32 PixelIdx;
ASSERT (RawData != NULL);
ASSERT (Scale == 1 || Scale == 2);
if (DataLength < sizeof (APPLE_DISK_LABEL)) {
return EFI_INVALID_PARAMETER;
}
Label = RawData;
Image->Width = SwapBytes16 (Label->Width);
Image->Height = SwapBytes16 (Label->Height);
if ( (Image->Width > APPLE_DISK_LABEL_MAX_WIDTH * Scale)
|| (Image->Height > APPLE_DISK_LABEL_MAX_HEIGHT * Scale)
|| (DataLength != sizeof (APPLE_DISK_LABEL) + Image->Width * Image->Height))
{
DEBUG ((DEBUG_INFO, "OCUI: Invalid label has %dx%d dims at %u size\n", Image->Width, Image->Height, DataLength));
return EFI_SECURITY_VIOLATION;
}
Image->Buffer = (EFI_GRAPHICS_OUTPUT_BLT_PIXEL *)AllocatePool (
Image->Width * Image->Height * sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL)
);
if (Image->Buffer == NULL) {
return EFI_OUT_OF_RESOURCES;
}
if (Inverted) {
for (PixelIdx = 0; PixelIdx < Image->Width * Image->Height; PixelIdx++) {
Image->Buffer[PixelIdx].Blue = 0;
Image->Buffer[PixelIdx].Green = 0;
Image->Buffer[PixelIdx].Red = 0;
Image->Buffer[PixelIdx].Reserved = 255 - gAppleDiskLabelImagePalette[Label->Data[PixelIdx]];
}
} else {
for (PixelIdx = 0; PixelIdx < Image->Width * Image->Height; PixelIdx++) {
Image->Buffer[PixelIdx].Blue = 255 - gAppleDiskLabelImagePalette[Label->Data[PixelIdx]];
Image->Buffer[PixelIdx].Green = 255 - gAppleDiskLabelImagePalette[Label->Data[PixelIdx]];
Image->Buffer[PixelIdx].Red = 255 - gAppleDiskLabelImagePalette[Label->Data[PixelIdx]];
Image->Buffer[PixelIdx].Reserved = 255 - gAppleDiskLabelImagePalette[Label->Data[PixelIdx]];
}
}
return EFI_SUCCESS;
}
EFI_STATUS
GuiPngToImage (
OUT GUI_IMAGE *Image,
IN VOID *ImageData,
IN UINTN ImageDataSize,
IN BOOLEAN PremultiplyAlpha
)
{
EFI_STATUS Status;
EFI_GRAPHICS_OUTPUT_BLT_PIXEL *BufferWalker;
UINTN Index;
UINT8 TmpChannel;
Status = OcDecodePng (
ImageData,
ImageDataSize,
(VOID **)&Image->Buffer,
&Image->Width,
&Image->Height,
NULL
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_INFO, "OCUI: DecodePNG - %r\n", Status));
return Status;
}
if (PremultiplyAlpha) {
BufferWalker = Image->Buffer;
for (Index = 0; Index < (UINTN)Image->Width * Image->Height; ++Index) {
TmpChannel = (UINT8)((BufferWalker->Blue * BufferWalker->Reserved) / 0xFF);
BufferWalker->Blue = (UINT8)((BufferWalker->Red * BufferWalker->Reserved) / 0xFF);
BufferWalker->Green = (UINT8)((BufferWalker->Green * BufferWalker->Reserved) / 0xFF);
BufferWalker->Red = TmpChannel;
++BufferWalker;
}
}
return EFI_SUCCESS;
}
EFI_STATUS
GuiCreateHighlightedImage (
OUT GUI_IMAGE *SelectedImage,
IN CONST GUI_IMAGE *SourceImage,
IN CONST EFI_GRAPHICS_OUTPUT_BLT_PIXEL *HighlightPixel
)
{
EFI_GRAPHICS_OUTPUT_BLT_PIXEL PremulPixel;
EFI_GRAPHICS_OUTPUT_BLT_PIXEL *Buffer;
UINT32 ColumnOffset;
BOOLEAN OneSet;
UINT32 FirstUnsetX;
UINT32 IndexY;
UINT32 RowOffset;
ASSERT (SelectedImage != NULL);
ASSERT (SourceImage != NULL);
ASSERT (SourceImage->Buffer != NULL);
ASSERT (HighlightPixel != NULL);
//
// The multiplication cannot wrap around because the original allocation sane.
//
Buffer = AllocateCopyPool (
SourceImage->Width * SourceImage->Height * sizeof (*SourceImage->Buffer),
SourceImage->Buffer
);
if (Buffer == NULL) {
return EFI_OUT_OF_RESOURCES;
}
PremulPixel.Blue = (UINT8)((HighlightPixel->Blue * HighlightPixel->Reserved) / 0xFF);
PremulPixel.Green = (UINT8)((HighlightPixel->Green * HighlightPixel->Reserved) / 0xFF);
PremulPixel.Red = (UINT8)((HighlightPixel->Red * HighlightPixel->Reserved) / 0xFF);
PremulPixel.Reserved = HighlightPixel->Reserved;
for (
IndexY = 0, RowOffset = 0;
IndexY < SourceImage->Height;
++IndexY, RowOffset += SourceImage->Width
)
{
FirstUnsetX = 0;
OneSet = FALSE;
for (ColumnOffset = 0; ColumnOffset < SourceImage->Width; ++ColumnOffset) {
if (SourceImage->Buffer[RowOffset + ColumnOffset].Reserved != 0) {
OneSet = TRUE;
GuiBlendPixelSolid (&Buffer[RowOffset + ColumnOffset], &PremulPixel);
if (FirstUnsetX != 0) {
//
// Set all fully transparent pixels between two not fully transparent
// pixels to the highlighter pixel.
//
while (FirstUnsetX < ColumnOffset) {
CopyMem (
&Buffer[RowOffset + FirstUnsetX],
&PremulPixel,
sizeof (*Buffer)
);
++FirstUnsetX;
}
FirstUnsetX = 0;
}
} else if ((FirstUnsetX == 0) && OneSet) {
FirstUnsetX = ColumnOffset;
}
}
}
SelectedImage->Width = SourceImage->Width;
SelectedImage->Height = SourceImage->Height;
SelectedImage->Buffer = Buffer;
return EFI_SUCCESS;
}