Icon refactor

This commit is contained in:
elasota
2021-06-06 01:12:21 -04:00
parent 2e9954677d
commit b616c6bf6e
19 changed files with 660 additions and 345 deletions

View File

@@ -1,24 +1,55 @@
As with Glider PRO's Room Editor, custom backgrounds, sounds, and TV videos
are supported.
CREATING CUSTOM RESOURCES FOR YOUR HOUSES
------------------------------------------------------------------------------
When editing a house, you can create custom resources for it by creating a ZIP
archive with the name of the house .gpf and .gpd files and giving it a .gpa
extension. This is referred to as a "resource archive."
Since Glider PRO used formats that were very Mac-specific, Aerofoil has
replaced most of them with more current formats.
Within a resource archive, you can place resources in a subfolder named after
the resource type, and resource files within the subfolder with a specified
ID.
To add resources to a house, create a ZIP file with the extension ".gpa" with
the same name as the .gpd and .gpf files that already exist for the house.
Supported resource types and their corresponding folder names:
"icl4": 32x32 4-bit-per-pixel icon graphics in BMP format.
"icl8": 32x32 8-bit-per-pixel icon graphics in BMP format.
"ICN$23": 32x64 black and white icon graphics and masks. The top half of the
image is the image, and bottom half is the mask. The mask should
be white for transparent pixels and black for opaque pixels.
"ics4": 16x16 4-bit-per-pixel icon graphics in BMP format.
"ics8": 16x16 8-bit-per-pixel icon graphics in BMP format.
"ics$23": 16x32 black and white icon graphics and masks. The top half of the
image is the image, and bottom half is the mask. The mask should
be white for transparent pixels and black for opaque pixels.
"PICT": BMP format images.
"snd$20": Sounds in 8-bit mono unsigned 22254Hz WAV format.
You can add resources by adding them to a folder named as the resource type,
with the appropriate extension.
For example, for a "PICT" resource of ID "3000", create a file named "3000.bmp"
and put it in the "PICT" directory in the .gpa archive.
For example, to create a custom image with the resource ID 1200, create a BMP
format image named "1200.bmp" and place it in the "PICT" directory inside of
the resource archive.
PICT resources, used for custom decorations and backgrounds, must be BMP files.
IDs can range from -32768 to 32767.
Sounds should go in a directory named "snd$20"
Sounds must be WAV format, monaural, 8-bit unsigned PCM, 22255 Hz.
Sounds recorded at a different sample rate will play back at the wrong speed.
Sounds that are not monaural or 8-bit unsigned PCM will fail to load.
To create a custom icon, create an "icl8" or "ICN$23" resource with the ID
-16455.
Resource IDs must be between -32768 and 32767. Other resource IDs will fail
to load.
PACKAGING YOUR HOUSE FOR DISTRIBUTION
-------------------------------------------------------------------------------
To package a house for single-file distribution, use the "MergeGPF" tool to
combine the metadata, house data, and resources into a single GPF file. Doing
this will make your house read-only, so make a copy first!
If you accidentally make your house read-only by doing this, then you can
return it to an editable state via the following steps:
- Open the GPF file using a ZIP archive tool.
- Extract the "!data" file from the GPF and change it to the name of the house
with a ".gpd" extension.
- If the house has any custom resources, then duplicate the GPF file and rename
it to the house name with a ".gpa" extension, then open it with a ZIP archive
tool and remove the "!!meta" and "!data" files from the ".gpa" archive.
- Remove the "!data" file and any custom resources from the GPF file.
Alternately, you can export a house to play with the original Glider PRO by
using the "Export Glider PRO House..." option in the Import/Export menu. Doing
this will output a MacBinary file to the "Export" subdirectory of the "Aerofoil"
directory in your Documents directory.

View File

@@ -1,3 +1,14 @@
ADDING A THIRD-PARTY HOUSE TO AEROFOIL
-------------------------------------------------------------------------------
To add a third-party house to Aerofoil, copy the house .gpf file, and .gpd/.gpa
files, if necessary, to the "Houses" directory inside of the "Aerofoil"
directory in your "Documents" directory, then restart Aerofoil.
IMPORTING EXISTING COMMUNITY CONTENT TO AEROFOIL
-------------------------------------------------------------------------------
If you want to import an existing third-party Glider PRO house to Aerofoil,
a few steps are required.
@@ -27,6 +38,8 @@ house with custom PICT resources that isn't supported, please submit a sample
to Aerofoil's issue tracker.
IMPORTING TV QUICKTIME MOVIES
-------------------------------------------------------------------------------
Converting QuickTime movies for displaying on in-game TVs is a bit more complex.
First, you need to convert the movie to a sequence of BMP images, which you
can do with third-party tools such as FFMPEG. Second, you need to create a
@@ -70,8 +83,8 @@ Glider PRO didn't really do any validation of houses. Currently, Aerofoil
doesn't do any additional validation either, and it's possible that invalid
house data may lead to crashes or even remote code execution.
I will be doing a hardening pass on the loader for the 1.1 release. Until
then, please only load houses from trusted sources.
Aerofoil 1.1 has a significantly stricter validator, which will reject data
that seems excessively large or invalid.
Also, Glider PRO houses were able to take advantage of a resource overlaying
feature of the Macintosh operating system, where a resource being present in
@@ -79,6 +92,4 @@ the house file with the same ID as an application resource would cause the
resource to override the application resource.
Aerofoil's resource loader is much more restrictive: Currently, only
backgrounds, audio triggers, and icons may load from the house data. This
restriction will be loosened in the 1.1 release to allow resources to be
overrided if I can confirm that it's safe to override them.
backgrounds, audio triggers, and icons may load from the house data.

View File

@@ -57,22 +57,52 @@ int toolMain(int argc, const char **argv)
for (int i = 7; i < argc; i++)
{
const char *arg = argv[i];
if (!strcmp(arg, "locked"))
mfp.m_finderFlags |= PortabilityLayer::FINDER_FILE_FLAG_LOCKED;
if (!strcmp(arg, "alias"))
mfp.m_finderFlags |= PortabilityLayer::FINDER_FILE_FLAG_ALIAS;
else if (!strcmp(arg, "invisible"))
mfp.m_finderFlags |= PortabilityLayer::FINDER_FILE_FLAG_INVISIBLE;
else if (!strcmp(arg, "bundle"))
mfp.m_finderFlags |= PortabilityLayer::FINDER_FILE_FLAG_BUNDLE;
else if (!strcmp(arg, "system"))
mfp.m_finderFlags |= PortabilityLayer::FINDER_FILE_FLAG_SYSTEM;
else if (!strcmp(arg, "copyprotected"))
mfp.m_finderFlags |= PortabilityLayer::FINDER_FILE_FLAG_COPY_PROTECTED;
else if (!strcmp(arg, "busy"))
mfp.m_finderFlags |= PortabilityLayer::FINDER_FILE_FLAG_BUSY;
else if (!strcmp(arg, "changed"))
mfp.m_finderFlags |= PortabilityLayer::FINDER_FILE_FLAG_CHANGED;
else if (!strcmp(arg, "namelocked"))
mfp.m_finderFlags |= PortabilityLayer::FINDER_FILE_FLAG_NAME_LOCKED;
else if (!strcmp(arg, "stationary"))
mfp.m_finderFlags |= PortabilityLayer::FINDER_FILE_FLAG_STATIONARY;
else if (!strcmp(arg, "customicon"))
mfp.m_finderFlags |= PortabilityLayer::FINDER_FILE_FLAG_CUSTOM_ICON;
else if (!strcmp(arg, "inited"))
mfp.m_finderFlags |= PortabilityLayer::FINDER_FILE_FLAG_INITED;
else if (!strcmp(arg, "noinits"))
mfp.m_finderFlags |= PortabilityLayer::FINDER_FILE_FLAG_NO_INITS;
else if (!strcmp(arg, "shared"))
mfp.m_finderFlags |= PortabilityLayer::FINDER_FILE_FLAG_SHARED;
else if (!strcmp(arg, "locked"))
mfp.m_protected = 1;
else if (!strcmp(arg, "color"))
{
int color = 0;
if (i < argc - 1)
{
i++;
color = atoi(argv[i + 1]);
}
else
{
fprintf(stderr, "Color should be followed by a number ranging from 0 to 7");
return -1;
}
if (color & 4)
mfp.m_finderFlags |= PortabilityLayer::FINDER_FILE_FLAG_COLOR_BIT2;
if (color & 2)
mfp.m_finderFlags |= PortabilityLayer::FINDER_FILE_FLAG_COLOR_BIT1;
if (color & 1)
mfp.m_finderFlags |= PortabilityLayer::FINDER_FILE_FLAG_COLOR_BIT0;
}
else
{
fprintf(stderr, "Unknown flag: %s", arg);
return -1;
}
}
PortabilityLayer::MacFilePropertiesSerialized mps;

View File

@@ -33,6 +33,7 @@
#include "MacBinary2.h"
#include "QDPictOpcodes.h"
#include "QDPixMap.h"
#include "QDStandardPalette.h"
#include "PLStandardColors.h"
#include "ZipFile.h"
@@ -3192,7 +3193,7 @@ static ExportHouseResult_t TryExportPICT(GpVector<uint8_t> &resData, const THand
const Rect rect = bmp->GetRect();
DrawSurface *surface = nullptr;
if (NewGWorld(&surface, GpPixelFormats::kRGB32, &rect, nullptr) != PLErrors::kNone)
if (NewGWorld(&surface, GpPixelFormats::kRGB32, rect) != PLErrors::kNone)
return ExportHouseResults::kMemError;
surface->DrawPicture(bmpHandle, rect, false);
@@ -3204,6 +3205,89 @@ static ExportHouseResult_t TryExportPICT(GpVector<uint8_t> &resData, const THand
return result;
}
static ExportHouseResult_t TryExportIcon(GpVector<uint8_t> &resData, const THandle<void> &resHandle, uint8_t width, uint8_t height, uint8_t bpp)
{
DrawSurface *surface = nullptr;
const Rect rect = Rect::Create(0, 0, height, width);
const BitmapImage *bmp = *resHandle.StaticCast<BitmapImage>();
if (bmp->GetRect() != rect)
return ExportHouseResults::kResourceError;
if (NewGWorld(&surface, GpPixelFormats::kRGB32, rect) != PLErrors::kNone)
return ExportHouseResults::kMemError;
surface->DrawPicture(resHandle.StaticCast<BitmapImage>(), rect, false);
if (!resData.Resize(width * height * bpp / 8))
{
DisposeGWorld(surface);
return ExportHouseResults::kMemError;
}
memset(resData.Buffer(), 0, width * height * bpp / 8);
const PixMap *pixMap = *surface->m_port.GetPixMap();
for (size_t row = 0; row < height; row++)
{
const PortabilityLayer::RGBAColor *srcColors = reinterpret_cast<const const PortabilityLayer::RGBAColor*>(static_cast<const uint8_t*>(pixMap->m_data) + pixMap->m_pitch * row);
for (size_t col = 0; col < width; col++)
{
const PortabilityLayer::RGBAColor &color = srcColors[col];
const size_t pixelIndex = col + row * width;
switch (bpp)
{
case 8:
{
const uint8_t colorIndex = PortabilityLayer::StandardPalette::GetInstance()->MapColorAnalytic(color);
resData[pixelIndex] = colorIndex;
}
break;
case 4:
{
uint8_t colorIndex = 0;
uint32_t bestError = 255 * 255 * 3 + 1;
const PortabilityLayer::RGBAColor *palette = PortabilityLayer::Icon4BitPalette::GetInstance()->GetColors();
const uint8_t candidateColor[3] = { color.r, color.g, color.b };
for (uint8_t i = 0; i < 16; i++)
{
uint32_t error = 0;
const int16_t deltas[3] = { palette[i].r - color.r, palette[i].g - color.g, palette[i].b - color.b };
for (int ch = 0; ch < 3; ch++)
error += static_cast<uint32_t>(deltas[ch] * deltas[ch]);
if (error < bestError)
{
bestError = error;
colorIndex = i;
}
}
resData[pixelIndex / 2] |= colorIndex << (4 - (pixelIndex & 1) * 4);
}
break;
case 1:
{
if (color.r + color.g + color.b < 382)
resData[pixelIndex / 8] |= 1 << (7 - (pixelIndex & 7));
}
break;
default:
return ExportHouseResults::kInternalError;
}
}
}
DisposeGWorld(surface);
return ExportHouseResults::kOK;
}
static ExportHouseResult_t TryExportResource(GpVector<uint8_t> &resData, PortabilityLayer::IResourceArchive *resArchive, const PortabilityLayer::ResTypeID &resTypeID, int16_t resID)
{
THandle<void> resHandle = resArchive->LoadResource(resTypeID, resID);
@@ -3224,6 +3308,35 @@ static ExportHouseResult_t TryExportResource(GpVector<uint8_t> &resData, Portabi
return exportResult;
}
struct IconTypeSpec
{
uint8_t m_width;
uint8_t m_height;
uint8_t m_bpp;
PortabilityLayer::ResTypeID m_resTypeID;
};
const IconTypeSpec iconTypeSpecs[] =
{
{ 32, 64, 1, PortabilityLayer::ResTypeID('ICN#') },
{ 32, 32, 4, PortabilityLayer::ResTypeID('icl4') },
{ 32, 32, 8, PortabilityLayer::ResTypeID('icl8') },
{ 16, 32, 1, PortabilityLayer::ResTypeID('ics#') },
{ 16, 16, 4, PortabilityLayer::ResTypeID('ics4') },
{ 16, 16, 8, PortabilityLayer::ResTypeID('ics8') },
};
for (int i = 0; i < sizeof(iconTypeSpecs) / sizeof(iconTypeSpecs[0]); i++)
{
const IconTypeSpec &spec = iconTypeSpecs[i];
if (resTypeID == spec.m_resTypeID)
{
ExportHouseResult_t exportResult = TryExportIcon(resData, resHandle, spec.m_width, spec.m_height, spec.m_bpp);
resHandle.Dispose();
return exportResult;
}
}
const size_t size = resHandle.MMBlock()->m_size;
if (!resData.Resize(size))
{
@@ -3239,8 +3352,10 @@ static ExportHouseResult_t TryExportResource(GpVector<uint8_t> &resData, Portabi
return ExportHouseResults::kOK;
}
ExportHouseResult_t TryExportResources(GpIOStream *stream, PortabilityLayer::IResourceArchive *resArchive)
ExportHouseResult_t TryExportResources(GpIOStream *stream, PortabilityLayer::IResourceArchive *resArchive, bool &hasCustomIcon)
{
hasCustomIcon = false;
if (!resArchive)
return ExportHouseResults::kOK;
@@ -3258,12 +3373,36 @@ ExportHouseResult_t TryExportResources(GpIOStream *stream, PortabilityLayer::IRe
GpUFilePos_t resForkDataStart = 0;
PortabilityLayer::ResTypeID exportableResTypes[] =
{
'PICT',
'snd ',
'ICN#',
'icl4',
'icl8',
'ics#',
'ics4',
'ics8'
};
const size_t numExportableResTypes = sizeof(exportableResTypes) / sizeof(exportableResTypes[0]);
PortabilityLayer::ResTypeID resTypeID;
int16_t resID = 0;
bool isFirstResource = true;
while (iterator->GetOne(resTypeID, resID))
{
if (resTypeID != PortabilityLayer::ResTypeID('PICT') && resTypeID != PortabilityLayer::ResTypeID('snd '))
bool isExportable = false;
for (size_t i = 0; i < numExportableResTypes; i++)
{
if (resTypeID == exportableResTypes[i])
{
isExportable = true;
break;
}
}
if (!isExportable)
continue;
if (isFirstResource)
@@ -3326,6 +3465,9 @@ ExportHouseResult_t TryExportResources(GpIOStream *stream, PortabilityLayer::IRe
if (!stream->WriteExact(padding, 4 - unpaddedExcess))
return ExportHouseResults::kIOError;
}
if (resTypeID == PortabilityLayer::ResTypeID('ICN#') && resID == -16455)
hasCustomIcon = true;
}
iterator->Destroy();
@@ -3549,10 +3691,12 @@ ExportHouseResult_t TryExportHouseToStream(GpIOStream *stream)
const GpUFilePos_t resForkPos = stream->Tell();
bool hasCustomIcon = false;
// Serialize resources
if (houseResFork != nullptr)
{
ExportHouseResult_t resExportResult = TryExportResources(stream, houseResFork);
ExportHouseResult_t resExportResult = TryExportResources(stream, houseResFork, hasCustomIcon);
if (resExportResult != ExportHouseResults::kOK)
return resExportResult;
}
@@ -3566,6 +3710,11 @@ ExportHouseResult_t TryExportHouseToStream(GpIOStream *stream)
return ExportHouseResults::kIOError;
}
uint16_t finderFlags = 0;
if (hasCustomIcon)
finderFlags |= PortabilityLayer::FINDER_FILE_FLAG_CUSTOM_ICON;
PortabilityLayer::MacFileInfo fileInfo;
fileInfo.m_fileName.Set(thisHouseName[0], reinterpret_cast<const char*>(thisHouseName + 1));
fileInfo.m_commentSize = 0;
@@ -3575,7 +3724,7 @@ ExportHouseResult_t TryExportHouseToStream(GpIOStream *stream)
memcpy(fileInfo.m_properties.m_fileCreator, "ozm5", 4);
fileInfo.m_properties.m_xPos = 0;
fileInfo.m_properties.m_yPos = 0;
fileInfo.m_properties.m_finderFlags = 0;
fileInfo.m_properties.m_finderFlags = finderFlags;
fileInfo.m_properties.m_protected = 0;
fileInfo.m_properties.m_createdTimeMacEpoch = fileInfo.m_properties.m_modifiedTimeMacEpoch = PLDrivers::GetSystemServices()->GetTime();

View File

@@ -222,13 +222,13 @@ PLError_t CreateOffScreenGWorld (DrawSurface **theGWorld, Rect *bounds)
{
GpPixelFormat_t pixelFormat = PortabilityLayer::DisplayDeviceManager::GetInstance()->GetPixelFormat();
return NewGWorld(theGWorld, pixelFormat, bounds, nil);
return NewGWorld(theGWorld, pixelFormat, *bounds);
}
PLError_t CreateOffScreenGWorldCustomDepth(DrawSurface **theGWorld, Rect *bounds, GpPixelFormat_t pixelFormat)
{
return NewGWorld(theGWorld, pixelFormat, bounds, nil);
return NewGWorld(theGWorld, pixelFormat, *bounds);
}
//-------------------------------------------------------------- KillOffScreenPixMap
@@ -357,12 +357,12 @@ bool LargeIconPlot (DrawSurface *surface, PortabilityLayer::IResourceArchive *re
Handle hdl = resFile->LoadResource('icl8', resID);
if (hdl)
{
THandle<PortabilityLayer::PixMapImpl> img = PortabilityLayer::IconLoader::GetInstance()->LoadSimpleColorIcon(hdl);
THandle<BitmapImage> img = hdl.StaticCast<BitmapImage>();
if (img)
{
CopyBits(*img, *surface->m_port.GetPixMap(), &(*img)->m_rect, &theRect, srcCopy);
img.Dispose();
const Rect rect = (*img)->GetRect();
if (rect.Width() == 32 && rect.Height() == 32)
surface->DrawPicture(img, theRect);
}
hdl.Dispose();
@@ -372,12 +372,22 @@ bool LargeIconPlot (DrawSurface *surface, PortabilityLayer::IResourceArchive *re
hdl = resFile->LoadResource('ICN#', resID);
if (hdl)
{
THandle<PortabilityLayer::PixMapImpl> img = PortabilityLayer::IconLoader::GetInstance()->LoadBWIcon(hdl);
THandle<BitmapImage> img = hdl.StaticCast<BitmapImage>();
if (img)
{
CopyBits(*img, *surface->m_port.GetPixMap(), &(*img)->m_rect, &theRect, srcCopy);
img.Dispose();
const Rect baseRect = (*img)->GetRect();
if (baseRect.Width() == 32 && baseRect.Height() == 64)
{
const Rect reducedRect = Rect::Create(0, 0, 32, 32);
DrawSurface *tempSurface = nullptr;
PLError_t err = NewGWorld(&tempSurface, surface->m_port.GetPixelFormat(), reducedRect);
if (err == PLErrors::kNone)
{
tempSurface->DrawPicture(img, baseRect);
CopyBits(*tempSurface->m_port.GetPixMap(), *surface->m_port.GetPixMap(), &reducedRect, &theRect, srcCopy);
DisposeGWorld(tempSurface);
}
}
}
hdl.Dispose();

View File

@@ -44,8 +44,6 @@ namespace PortabilityLayer
{
public:
bool LoadColorIcon(const int16_t id, THandle<PixMapImpl> &outColorImage, THandle<PixMapImpl> &outBWImage, THandle<PixMapImpl> &outMaskImage) override;
THandle<PixMapImpl> LoadSimpleColorIcon(const THandle<void> &hdl) override;
THandle<PixMapImpl> LoadBWIcon(const THandle<void> &hdl) override;
static IconLoaderImpl *GetInstance();
@@ -317,87 +315,6 @@ namespace PortabilityLayer
return true;
}
THandle<PixMapImpl> IconLoaderImpl::LoadSimpleColorIcon(const THandle<void> &hdl)
{
if (hdl == nullptr || hdl.MMBlock()->m_size != 1024)
return THandle<PixMapImpl>();
const Rect rect = Rect::Create(0, 0, 32, 32);
GpPixelFormat_t pixelFormat = DisplayDeviceManager::GetInstance()->GetPixelFormat();
THandle<PixMapImpl> pixMap = PixMapImpl::Create(rect, pixelFormat);
if (!pixMap)
return THandle<PixMapImpl>();
const uint8_t *inData = static_cast<const uint8_t*>(*hdl);
uint8_t *outData = static_cast<uint8_t*>((*pixMap)->GetPixelData());
const size_t outPitch = (*pixMap)->GetPitch();
if (pixelFormat == GpPixelFormats::kRGB32)
{
const PortabilityLayer::RGBAColor *palette = StandardPalette::GetInstance()->GetColors();
for (size_t row = 0; row < 32; row++)
{
uint32_t *outU32 = reinterpret_cast<uint32_t*>(outData);
for (size_t col = 0; col < 32; col++)
outU32[col] = palette[inData[col]].AsUInt32();
inData += 32;
outData += outPitch;
}
}
else if (pixelFormat == GpPixelFormats::k8BitStandard)
{
for (size_t row = 0; row < 32; row++)
{
for (size_t col = 0; col < 32; col++)
outData[col] = inData[col];
inData += 32;
outData += outPitch;
}
}
else
{
PL_NotYetImplemented();
}
return pixMap;
}
THandle<PixMapImpl> IconLoaderImpl::LoadBWIcon(const THandle<void> &hdl)
{
if (hdl == nullptr || hdl.MMBlock()->m_size != 128)
return THandle<PixMapImpl>();
const Rect rect = Rect::Create(0, 0, 32, 32);
THandle<PixMapImpl> pixMap = PixMapImpl::Create(rect, GpPixelFormats::kBW1);
if (!pixMap)
return THandle<PixMapImpl>();
const uint8_t *inData = static_cast<const uint8_t*>(*hdl);
uint8_t *outData = static_cast<uint8_t*>((*pixMap)->GetPixelData());
const size_t outPitch = (*pixMap)->GetPitch();
for (size_t row = 0; row < 32; row++)
{
for (size_t col = 0; col < 32; col++)
{
if (inData[col / 8] & (0x80 >> (col & 7)))
outData[col] = 0xff;
else
outData[col] = 0x00;
}
inData += 4;
outData += outPitch;
}
return pixMap;
}
IconLoaderImpl *IconLoaderImpl::GetInstance()
{
return &ms_instance;

View File

@@ -9,13 +9,11 @@ namespace PortabilityLayer
{
class PixMapImpl;
class SimpleImage;
class IconLoader
{
public:
virtual bool LoadColorIcon(const int16_t id, THandle<PixMapImpl> &outColorImage, THandle<PixMapImpl> &outBWImage, THandle<PixMapImpl> &outMaskImage) = 0;
virtual THandle<PixMapImpl> LoadSimpleColorIcon(const THandle<void> &hdl) = 0;
virtual THandle<PixMapImpl> LoadBWIcon(const THandle<void> &hdl) = 0;
static IconLoader *GetInstance();
};

View File

@@ -11,14 +11,18 @@ namespace PortabilityLayer
enum FinderFileFlags
{
FINDER_FILE_FLAG_LOCKED = (1 << 15),
FINDER_FILE_FLAG_ALIAS = (1 << 15),
FINDER_FILE_FLAG_INVISIBLE = (1 << 14),
FINDER_FILE_FLAG_BUNDLE = (1 << 13),
FINDER_FILE_FLAG_SYSTEM = (1 << 12),
FINDER_FILE_FLAG_COPY_PROTECTED = (1 << 11),
FINDER_FILE_FLAG_BUSY = (1 << 10),
FINDER_FILE_FLAG_CHANGED = (1 << 9),
FINDER_FILE_FLAG_NAME_LOCKED = (1 << 12),
FINDER_FILE_FLAG_STATIONARY = (1 << 11),
FINDER_FILE_FLAG_CUSTOM_ICON = (1 << 10),
FINDER_FILE_FLAG_INITED = (1 << 8),
FINDER_FILE_FLAG_NO_INITS = (1 << 7),
FINDER_FILE_FLAG_SHARED = (1 << 6),
FINDER_FILE_FLAG_COLOR_BIT2 = (1 << 3),
FINDER_FILE_FLAG_COLOR_BIT1 = (1 << 2),
FINDER_FILE_FLAG_COLOR_BIT0 = (1 << 1),
};
struct MacFileProperties

View File

@@ -892,13 +892,41 @@ namespace PortabilityLayer
void *storage = static_cast<GraphicType_t*>(NewPtr(sizeof(GraphicType_t)));
if (storage)
{
memcpy(m_iconMask, static_cast<const uint8_t*>(*icsHandle) + 32, 32);
memcpy(m_iconColors, static_cast<const uint8_t*>(*ics8Handle), 16 * 16);
DrawSurface *iconMaskTemp = nullptr;
DrawSurface *iconColorTemp = nullptr;
GraphicType_t *graphic = new (storage) GraphicType_t(m_iconColors);
if (NewGWorld(&iconMaskTemp, GpPixelFormats::kBW1, Rect::Create(0, 0, 16, 16)) == PLErrors::kNone)
{
if (NewGWorld(&iconColorTemp, GpPixelFormats::k8BitStandard, Rect::Create(0, 0, 16, 16)) == PLErrors::kNone)
{
iconMaskTemp->DrawPicture(icsHandle.StaticCast<BitmapImage>(), Rect::Create(-16, 0, 16, 16));
iconColorTemp->DrawPicture(ics8Handle.StaticCast<BitmapImage>(), Rect::Create(0, 0, 16, 16));
m_iconGraphic = graphic;
const PixMap *maskPixMap = (*iconMaskTemp->m_port.GetPixMap());
const PixMap *colorPixMap = (*iconColorTemp->m_port.GetPixMap());
memset(m_iconMask, 0, 16 * 16 / 8);
for (size_t row = 0; row < 16; row++)
{
const uint8_t *maskSourceRow = static_cast<const uint8_t*>(maskPixMap->m_data) + maskPixMap->m_pitch * row;
for (size_t col = 0; col < 16; col++)
{
if (maskSourceRow[col] != 0)
m_iconMask[row * 2 + col / 8] |= (1 << (7 - (col & 7)));
}
memcpy(m_iconColors + row * 16, static_cast<const uint8_t*>(colorPixMap->m_data) + colorPixMap->m_pitch * row, 16);
}
GraphicType_t *graphic = new (storage) GraphicType_t(m_iconColors);
m_iconGraphic = graphic;
DisposeGWorld(iconColorTemp);
}
DisposeGWorld(iconMaskTemp);
}
}
}
@@ -923,7 +951,7 @@ namespace PortabilityLayer
if (m_menuBarGraf == nullptr)
{
if (qdManager->NewGWorld(&m_menuBarGraf, pixelFormat, menuRect, nullptr) != 0)
if (qdManager->NewGWorld(&m_menuBarGraf, pixelFormat, menuRect) != 0)
return;
}
@@ -1529,7 +1557,7 @@ namespace PortabilityLayer
{
GpPixelFormat_t pixelFormat = DisplayDeviceManager::GetInstance()->GetPixelFormat();
if (qdManager->NewGWorld(&m_menuGraf, pixelFormat, menuRect, nullptr) != 0)
if (qdManager->NewGWorld(&m_menuGraf, pixelFormat, menuRect) != 0)
return;
}

View File

@@ -233,7 +233,7 @@ void AnimationManager::RefreshPlayer(AnimationPlayer *player)
else
{
DrawSurface *renderSurface = nullptr;
if (PortabilityLayer::QDManager::GetInstance()->NewGWorld(&renderSurface, surface->m_port.GetPixelFormat(), player->m_renderRect, nullptr) != PLErrors::kNone)
if (PortabilityLayer::QDManager::GetInstance()->NewGWorld(&renderSurface, surface->m_port.GetPixelFormat(), player->m_renderRect) != PLErrors::kNone)
return;
renderSurface->DrawPicture(img, player->m_renderRect);

View File

@@ -18,9 +18,9 @@
#include <string.h>
#include <assert.h>
PLError_t NewGWorld(DrawSurface **gworld, GpPixelFormat_t pixelFormat, const Rect *bounds, CTabHandle colorTable)
PLError_t NewGWorld(DrawSurface **gworld, GpPixelFormat_t pixelFormat, const Rect &bounds)
{
return PortabilityLayer::QDManager::GetInstance()->NewGWorld(gworld, pixelFormat, *bounds, colorTable);
return PortabilityLayer::QDManager::GetInstance()->NewGWorld(gworld, pixelFormat, bounds);
}
void DisposeGWorld(DrawSurface *gworld)

View File

@@ -17,7 +17,7 @@ typedef CTabPtr *CTabHandle;
typedef PixMap *PixMapPtr;
typedef PixMapPtr *PixMapHandle;
PLError_t NewGWorld(DrawSurface **gworld, GpPixelFormat_t pixelFormat, const Rect *bounds, CTabHandle colorTable);
PLError_t NewGWorld(DrawSurface **gworld, GpPixelFormat_t pixelFormat, const Rect &bounds);
void DisposeGWorld(DrawSurface *gworld);
PixMapHandle GetGWorldPixMap(DrawSurface *gworld);

View File

@@ -584,7 +584,7 @@ void DrawSurface::DrawPicture(THandle<BitmapImage> pictHdl, const Rect &bounds,
PL_NotYetImplemented_TODO("Palette");
DrawSurface *scaleSurface = nullptr;
if (PortabilityLayer::QDManager::GetInstance()->NewGWorld(&scaleSurface, this->m_port.GetPixelFormat(), picRect, nullptr) != PLErrors::kNone)
if (PortabilityLayer::QDManager::GetInstance()->NewGWorld(&scaleSurface, this->m_port.GetPixelFormat(), picRect) != PLErrors::kNone)
return;
scaleSurface->DrawPicture(pictHdl, picRect);

View File

@@ -311,7 +311,7 @@ namespace PortabilityLayer
extension = ".wav";
outValidationRule = ResourceValidationRules::kWAV;
}
else if (resTypeID == ResTypeID('Date') || resTypeID == ResTypeID('PICT'))
else if (resTypeID == ResTypeID('Date') || resTypeID == ResTypeID('PICT') || resTypeID == ResTypeID('ICN#') || resTypeID == ResTypeID('icl8') || resTypeID == ResTypeID('icl4') || resTypeID == ResTypeID('ics#') || resTypeID == ResTypeID('ics8') || resTypeID == ResTypeID('ics4'))
{
extension = kPICTExtension;
outValidationRule = ResourceValidationRules::kBMP;

View File

@@ -15,7 +15,7 @@ namespace PortabilityLayer
QDManagerImpl();
void Init() override;
PLError_t NewGWorld(DrawSurface **gw, GpPixelFormat_t pixelFormat, const Rect &bounds, ColorTable **colorTable) override;
PLError_t NewGWorld(DrawSurface **gw, GpPixelFormat_t pixelFormat, const Rect &bounds) override;
void DisposeGWorld(DrawSurface *gw) override;
static QDManagerImpl *GetInstance();
@@ -32,7 +32,7 @@ namespace PortabilityLayer
{
}
PLError_t QDManagerImpl::NewGWorld(DrawSurface **gw, GpPixelFormat_t pixelFormat, const Rect &bounds, ColorTable **colorTable)
PLError_t QDManagerImpl::NewGWorld(DrawSurface **gw, GpPixelFormat_t pixelFormat, const Rect &bounds)
{
void *grafStorage = MemoryManager::GetInstance()->Alloc(sizeof(DrawSurface));
if (!grafStorage)

View File

@@ -15,7 +15,7 @@ namespace PortabilityLayer
{
public:
virtual void Init() = 0;
virtual PLError_t NewGWorld(DrawSurface **gw, GpPixelFormat_t pixelFormat, const Rect &bounds, ColorTable **colorTable) = 0;
virtual PLError_t NewGWorld(DrawSurface **gw, GpPixelFormat_t pixelFormat, const Rect &bounds) = 0;
virtual void DisposeGWorld(DrawSurface *gw) = 0;
static QDManager *GetInstance();

View File

@@ -299,4 +299,36 @@ namespace PortabilityLayer
}
StandardPalette StandardPalette::ms_instance;
Icon4BitPalette::Icon4BitPalette()
{
m_colors[0] = RGBAColor::Create(255, 255, 255, 255);
m_colors[1] = RGBAColor::Create(251, 243, 5, 255);
m_colors[2] = RGBAColor::Create(255, 100, 3, 255);
m_colors[3] = RGBAColor::Create(221, 9, 7, 255);
m_colors[4] = RGBAColor::Create(242, 8, 132, 255);
m_colors[5] = RGBAColor::Create(71, 0, 165, 255);
m_colors[6] = RGBAColor::Create(0, 0, 211, 255);
m_colors[7] = RGBAColor::Create(2, 171, 234, 255);
m_colors[8] = RGBAColor::Create(31, 183, 20, 255);
m_colors[9] = RGBAColor::Create(0, 100, 18, 255);
m_colors[10] = RGBAColor::Create(86, 44, 5, 255);
m_colors[11] = RGBAColor::Create(144, 113, 58, 255);
m_colors[12] = RGBAColor::Create(191, 191, 191, 255);
m_colors[13] = RGBAColor::Create(128, 128, 128, 255);
m_colors[14] = RGBAColor::Create(64, 64, 64, 255);
m_colors[15] = RGBAColor::Create(0, 0, 0, 255);
}
Icon4BitPalette *Icon4BitPalette::GetInstance()
{
return &ms_instance;
}
const RGBAColor *Icon4BitPalette::GetColors() const
{
return m_colors;
}
Icon4BitPalette Icon4BitPalette::ms_instance;
}

View File

@@ -56,4 +56,19 @@ namespace PortabilityLayer
uint8_t m_lut[16 * 16 * 16];
};
class Icon4BitPalette
{
public:
static const unsigned int kSize = 16;
Icon4BitPalette();
static Icon4BitPalette *GetInstance();
const RGBAColor *GetColors() const;
private:
static Icon4BitPalette ms_instance;
RGBAColor m_colors[kSize];
};
}

View File

@@ -8,6 +8,7 @@
#include "QDPictDecoder.h"
#include "QDPictEmitContext.h"
#include "QDPictEmitScanlineParameters.h"
#include "QDStandardPalette.h"
#include "MacFileInfo.h"
#include "ResourceFile.h"
#include "ResourceCompiledTypeList.h"
@@ -307,6 +308,193 @@ void ExportZipFile(const char *path, std::vector<PlannedEntry> &entries, const P
fclose(outF);
}
bool ExportBMP(size_t width, size_t height, size_t pitchInElements, const PortabilityLayer::RGBAColor *pixelData, std::vector<uint8_t> &outData)
{
outData.resize(0);
bool couldBe15Bit = true;
bool couldBeIndexed = true;
PortabilityLayer::BitmapColorTableEntry colorTable[256];
unsigned int numColors = 0;
for (size_t row = 0; row < height; row++)
{
const PortabilityLayer::RGBAColor *rowData = &pixelData[pitchInElements * row];
for (size_t col = 0; col < width; col++)
{
const PortabilityLayer::RGBAColor &pixel = rowData[col];
if (couldBe15Bit)
{
if (FiveToEight(pixel.r >> 3) != pixel.r || FiveToEight(pixel.g >> 3) != pixel.g || FiveToEight(pixel.b >> 3) != pixel.b)
{
couldBe15Bit = false;
if (!couldBeIndexed)
break;
}
}
if (couldBeIndexed)
{
bool matched = false;
for (unsigned int ci = 0; ci < numColors; ci++)
{
const PortabilityLayer::BitmapColorTableEntry &ctabEntry = colorTable[ci];
if (ctabEntry.m_r == pixel.r && ctabEntry.m_g == pixel.g && ctabEntry.m_b == pixel.b)
{
matched = true;
break;
}
}
if (matched == false)
{
if (numColors == 256)
{
couldBeIndexed = false;
if (!couldBe15Bit)
break;
}
else
{
PortabilityLayer::BitmapColorTableEntry &ctabEntry = colorTable[numColors++];
ctabEntry.m_r = pixel.r;
ctabEntry.m_g = pixel.g;
ctabEntry.m_b = pixel.b;
ctabEntry.m_reserved = 0;
}
}
}
}
if (!couldBeIndexed && !couldBe15Bit)
break;
}
unsigned int bpp = 24;
if (couldBeIndexed)
{
if (numColors <= 2)
bpp = 1;
else if (numColors <= 16)
bpp = 4;
else
bpp = 8;
}
else if (couldBe15Bit)
bpp = 16;
const size_t minimalBitsPerRow = bpp * width;
const size_t rowPitchBytes = ((minimalBitsPerRow + 31) / 32) * 4; // DWORD alignment
const size_t colorTableSize = (bpp < 16) ? numColors * 4 : 0;
const size_t fileHeaderSize = sizeof(PortabilityLayer::BitmapFileHeader);
const size_t infoHeaderSize = sizeof(PortabilityLayer::BitmapInfoHeader);
const size_t ctabSize = (bpp < 16) ? (numColors * 4) : 0;
const size_t imageDataSize = rowPitchBytes * height;
const size_t postCTabPaddingSize = 2;
const size_t imageFileSize = fileHeaderSize + infoHeaderSize + ctabSize + postCTabPaddingSize + imageDataSize;
outData.reserve(imageFileSize);
PortabilityLayer::BitmapFileHeader fileHeader;
fileHeader.m_id[0] = 'B';
fileHeader.m_id[1] = 'M';
fileHeader.m_fileSize = static_cast<uint32_t>(imageFileSize);
fileHeader.m_imageDataStart = static_cast<uint32_t>(fileHeaderSize + infoHeaderSize + ctabSize + postCTabPaddingSize);
fileHeader.m_reserved1 = 0;
fileHeader.m_reserved2 = 0;
VectorAppend(outData, reinterpret_cast<const uint8_t*>(&fileHeader), sizeof(fileHeader));
PortabilityLayer::BitmapInfoHeader infoHeader;
infoHeader.m_thisStructureSize = sizeof(infoHeader);
infoHeader.m_width = static_cast<uint32_t>(width);
infoHeader.m_height = static_cast<uint32_t>(height);
infoHeader.m_planes = 1;
infoHeader.m_bitsPerPixel = bpp;
infoHeader.m_compression = PortabilityLayer::BitmapConstants::kCompressionRGB;
infoHeader.m_imageSize = static_cast<uint32_t>(imageDataSize);
infoHeader.m_xPixelsPerMeter = 2835;
infoHeader.m_yPixelsPerMeter = 2835;
infoHeader.m_numColors = (bpp < 16) ? numColors : 0;
infoHeader.m_importantColorCount = 0;
VectorAppend(outData, reinterpret_cast<const uint8_t*>(&infoHeader), sizeof(infoHeader));
if (bpp < 16)
VectorAppend(outData, reinterpret_cast<const uint8_t*>(colorTable), sizeof(PortabilityLayer::BitmapColorTableEntry) * numColors);
for (size_t i = 0; i < postCTabPaddingSize; i++)
outData.push_back(0);
std::vector<uint8_t> rowPackData;
rowPackData.resize(rowPitchBytes);
for (size_t row = 0; row < height; row++)
{
for (size_t i = 0; i < rowPitchBytes; i++)
rowPackData[i] = 0;
const PortabilityLayer::RGBAColor *rowData = &pixelData[pitchInElements * (height - 1 - row)];
for (size_t col = 0; col < width; col++)
{
const PortabilityLayer::RGBAColor &pixel = rowData[col];
if (bpp == 24)
{
rowPackData[col * 3 + 0] = pixel.b;
rowPackData[col * 3 + 1] = pixel.g;
rowPackData[col * 3 + 2] = pixel.r;
}
else if (bpp == 16)
{
int packedValue = 0;
packedValue |= (pixel.b >> 3);
packedValue |= ((pixel.g >> 3) << 5);
packedValue |= ((pixel.r >> 3) << 10);
rowPackData[col * 2 + 0] = static_cast<uint8_t>(packedValue & 0xff);
rowPackData[col * 2 + 1] = static_cast<uint8_t>((packedValue >> 8) & 0xff);
}
else
{
unsigned int colorIndex = 0;
for (unsigned int ci = 0; ci < numColors; ci++)
{
const PortabilityLayer::BitmapColorTableEntry &ctabEntry = colorTable[ci];
if (ctabEntry.m_r == pixel.r && ctabEntry.m_g == pixel.g && ctabEntry.m_b == pixel.b)
{
colorIndex = ci;
break;
}
}
if (bpp == 8)
rowPackData[col] = colorIndex;
else if (bpp == 4)
rowPackData[col / 2] |= (colorIndex << (8 - (((col & 1) + 1) * 4)));
else if (bpp == 1)
{
if (colorIndex)
rowPackData[col / 8] |= (0x80 >> (col & 7));
}
}
}
VectorAppend(outData, &rowPackData[0], rowPackData.size());
}
return true;
}
class BMPDumperContext : public PortabilityLayer::QDPictEmitContext
{
public:
@@ -491,191 +679,7 @@ bool BMPDumperContext::AllocTempBuffers(uint8_t *&buffer1, size_t buffer1Size, u
bool BMPDumperContext::Export(std::vector<uint8_t> &outData) const
{
outData.resize(0);
bool couldBe15Bit = true;
bool couldBeIndexed = true;
PortabilityLayer::BitmapColorTableEntry colorTable[256];
unsigned int numColors = 0;
const size_t height = m_frame.Height();
const size_t width = m_frame.Width();
const size_t pitch = m_pitchInElements;
for (size_t row = 0; row < height; row++)
{
const PortabilityLayer::RGBAColor *rowData = &m_pixelData[m_pitchInElements * row];
for (size_t col = 0; col < width; col++)
{
const PortabilityLayer::RGBAColor &pixel = rowData[col];
if (couldBe15Bit)
{
if (FiveToEight(pixel.r >> 3) != pixel.r || FiveToEight(pixel.g >> 3) != pixel.g || FiveToEight(pixel.b >> 3) != pixel.b)
{
couldBe15Bit = false;
if (!couldBeIndexed)
break;
}
}
if (couldBeIndexed)
{
bool matched = false;
for (unsigned int ci = 0; ci < numColors; ci++)
{
const PortabilityLayer::BitmapColorTableEntry &ctabEntry = colorTable[ci];
if (ctabEntry.m_r == pixel.r && ctabEntry.m_g == pixel.g && ctabEntry.m_b == pixel.b)
{
matched = true;
break;
}
}
if (matched == false)
{
if (numColors == 256)
{
couldBeIndexed = false;
if (!couldBe15Bit)
break;
}
else
{
PortabilityLayer::BitmapColorTableEntry &ctabEntry = colorTable[numColors++];
ctabEntry.m_r = pixel.r;
ctabEntry.m_g = pixel.g;
ctabEntry.m_b = pixel.b;
ctabEntry.m_reserved = 0;
}
}
}
}
if (!couldBeIndexed && !couldBe15Bit)
break;
}
unsigned int bpp = 24;
if (couldBeIndexed)
{
if (numColors <= 2)
bpp = 1;
else if (numColors <= 16)
bpp = 4;
else
bpp = 8;
}
else if (couldBe15Bit)
bpp = 16;
const size_t minimalBitsPerRow = bpp * width;
const size_t rowPitchBytes = ((minimalBitsPerRow + 31) / 32) * 4; // DWORD alignment
const size_t colorTableSize = (bpp < 16) ? numColors * 4 : 0;
const size_t fileHeaderSize = sizeof(PortabilityLayer::BitmapFileHeader);
const size_t infoHeaderSize = sizeof(PortabilityLayer::BitmapInfoHeader);
const size_t ctabSize = (bpp < 16) ? (numColors * 4) : 0;
const size_t imageDataSize = rowPitchBytes * height;
const size_t postCTabPaddingSize = 2;
const size_t imageFileSize = fileHeaderSize + infoHeaderSize + ctabSize + postCTabPaddingSize + imageDataSize;
outData.reserve(imageFileSize);
PortabilityLayer::BitmapFileHeader fileHeader;
fileHeader.m_id[0] = 'B';
fileHeader.m_id[1] = 'M';
fileHeader.m_fileSize = static_cast<uint32_t>(imageFileSize);
fileHeader.m_imageDataStart = static_cast<uint32_t>(fileHeaderSize + infoHeaderSize + ctabSize + postCTabPaddingSize);
fileHeader.m_reserved1 = 0;
fileHeader.m_reserved2 = 0;
VectorAppend(outData, reinterpret_cast<const uint8_t*>(&fileHeader), sizeof(fileHeader));
PortabilityLayer::BitmapInfoHeader infoHeader;
infoHeader.m_thisStructureSize = sizeof(infoHeader);
infoHeader.m_width = static_cast<uint32_t>(width);
infoHeader.m_height = static_cast<uint32_t>(height);
infoHeader.m_planes = 1;
infoHeader.m_bitsPerPixel = bpp;
infoHeader.m_compression = PortabilityLayer::BitmapConstants::kCompressionRGB;
infoHeader.m_imageSize = static_cast<uint32_t>(imageDataSize);
infoHeader.m_xPixelsPerMeter = 2835;
infoHeader.m_yPixelsPerMeter = 2835;
infoHeader.m_numColors = (bpp < 16) ? numColors : 0;
infoHeader.m_importantColorCount = 0;
VectorAppend(outData, reinterpret_cast<const uint8_t*>(&infoHeader), sizeof(infoHeader));
if (bpp < 16)
VectorAppend(outData, reinterpret_cast<const uint8_t*>(colorTable), sizeof(PortabilityLayer::BitmapColorTableEntry) * numColors);
for (size_t i = 0; i < postCTabPaddingSize; i++)
outData.push_back(0);
std::vector<uint8_t> rowPackData;
rowPackData.resize(rowPitchBytes);
for (size_t row = 0; row < height; row++)
{
for (size_t i = 0; i < rowPitchBytes; i++)
rowPackData[i] = 0;
const PortabilityLayer::RGBAColor *rowData = &m_pixelData[m_pitchInElements * (height - 1 - row)];
for (size_t col = 0; col < width; col++)
{
const PortabilityLayer::RGBAColor &pixel = rowData[col];
if (bpp == 24)
{
rowPackData[col * 3 + 0] = pixel.b;
rowPackData[col * 3 + 1] = pixel.g;
rowPackData[col * 3 + 2] = pixel.r;
}
else if (bpp == 16)
{
int packedValue = 0;
packedValue |= (pixel.b >> 3);
packedValue |= ((pixel.g >> 3) << 5);
packedValue |= ((pixel.r >> 3) << 10);
rowPackData[col * 2 + 0] = static_cast<uint8_t>(packedValue & 0xff);
rowPackData[col * 2 + 1] = static_cast<uint8_t>((packedValue >> 8) & 0xff);
}
else
{
unsigned int colorIndex = 0;
for (unsigned int ci = 0; ci < numColors; ci++)
{
const PortabilityLayer::BitmapColorTableEntry &ctabEntry = colorTable[ci];
if (ctabEntry.m_r == pixel.r && ctabEntry.m_g == pixel.g && ctabEntry.m_b == pixel.b)
{
colorIndex = ci;
break;
}
}
if (bpp == 8)
rowPackData[col] = colorIndex;
else if (bpp == 4)
rowPackData[col / 2] |= (colorIndex << (8 - (((col & 1) + 1) * 4)));
else if (bpp == 1)
{
if (colorIndex)
rowPackData[col / 8] |= (0x80 >> (col & 7));
}
}
}
VectorAppend(outData, &rowPackData[0], rowPackData.size());
}
return true;
return ExportBMP(m_frame.Width(), m_frame.Height(), m_pitchInElements, &m_pixelData[0], outData);
}
bool ImportPICT(std::vector<uint8_t> &outBMP, const void *inData, size_t inSize)
@@ -1266,6 +1270,51 @@ bool ImportDialogItemTemplate(std::vector<uint8_t> &outTXT, const void *inData,
return true;
}
bool ImportIcon(std::vector<uint8_t> &outBMP, const void *inData, size_t inSize, uint8_t width, uint8_t height, uint8_t bpp)
{
size_t expectedSize = width * height * bpp / 8;
if (inSize != expectedSize)
return false;
PortabilityLayer::RGBAColor bwColors[] = { PortabilityLayer::RGBAColor::Create(255, 255, 255, 255), PortabilityLayer::RGBAColor::Create(0, 0, 0, 255) };
const PortabilityLayer::RGBAColor *palette = nullptr;
if (bpp == 1)
palette = bwColors;
else if (bpp == 4)
palette = PortabilityLayer::Icon4BitPalette::GetInstance()->GetColors();
else if (bpp == 8)
palette = PortabilityLayer::StandardPalette::GetInstance()->GetColors();
else
return false;
size_t numPixels = width * height;
std::vector<PortabilityLayer::RGBAColor> pixelData;
pixelData.resize(numPixels);
int bitOffset = 8;
size_t byteOffset = 0;
int mask = (1 << bpp) - 1;
for (size_t i = 0; i < numPixels; i++)
{
if (bitOffset == 0)
{
byteOffset++;
bitOffset = 8;
}
bitOffset -= bpp;
int value = static_cast<const uint8_t*>(inData)[byteOffset];
value = (value >> bitOffset) & mask;
pixelData[i] = palette[value];
}
return ExportBMP(width, height, width, &pixelData[0], outBMP);
}
void ReadFileToVector(FILE *f, std::vector<uint8_t> &vec)
{
fseek(f, 0, SEEK_END);
@@ -1452,6 +1501,24 @@ int ConvertSingleFile(const char *resPath, const PortabilityLayer::CombinedTimes
const PortabilityLayer::ResTypeID indexStringTypeID = PortabilityLayer::ResTypeID('STR#');
const PortabilityLayer::ResTypeID ditlTypeID = PortabilityLayer::ResTypeID('DITL');
struct IconTypeSpec
{
uint8_t m_width;
uint8_t m_height;
uint8_t m_bpp;
PortabilityLayer::ResTypeID m_resTypeID;
};
const IconTypeSpec iconTypeSpecs[] =
{
{ 32, 64, 1, PortabilityLayer::ResTypeID('ICN#') },
{ 32, 32, 4, PortabilityLayer::ResTypeID('icl4') },
{ 32, 32, 8, PortabilityLayer::ResTypeID('icl8') },
{ 16, 32, 1, PortabilityLayer::ResTypeID('ics#') },
{ 16, 16, 4, PortabilityLayer::ResTypeID('ics4') },
{ 16, 16, 8, PortabilityLayer::ResTypeID('ics8') },
};
for (size_t tlIndex = 0; tlIndex < typeListCount; tlIndex++)
{
const PortabilityLayer::ResourceCompiledTypeList &typeList = typeLists[tlIndex];
@@ -1527,17 +1594,40 @@ int ConvertSingleFile(const char *resPath, const PortabilityLayer::CombinedTimes
}
else
{
PlannedEntry entry;
bool isIcon = false;
char resName[256];
sprintf_s(resName, "%s/%i.bin", resTag.m_id, static_cast<int>(res.m_resID));
for (int i = 0; i < sizeof(iconTypeSpecs) / sizeof(iconTypeSpecs[0]); i++)
{
const IconTypeSpec &iconSpec = iconTypeSpecs[i];
if (typeList.m_resType == iconSpec.m_resTypeID)
{
isIcon = true;
entry.m_name = resName;
entry.m_uncompressedContents.resize(res.GetSize());
PlannedEntry entry;
char resName[256];
sprintf_s(resName, "%s/%i.bmp", resTag.m_id, static_cast<int>(res.m_resID));
memcpy(&entry.m_uncompressedContents[0], resData, resSize);
entry.m_name = resName;
contents.push_back(entry);
if (ImportIcon(entry.m_uncompressedContents, resData, resSize, iconSpec.m_width, iconSpec.m_height, iconSpec.m_bpp))
contents.push_back(entry);
}
}
if (!isIcon)
{
PlannedEntry entry;
char resName[256];
sprintf_s(resName, "%s/%i.bin", resTag.m_id, static_cast<int>(res.m_resID));
entry.m_name = resName;
entry.m_uncompressedContents.resize(res.GetSize());
memcpy(&entry.m_uncompressedContents[0], resData, resSize);
contents.push_back(entry);
}
}
}
}