mirror of
https://github.com/elasota/Aerofoil.git
synced 2025-12-14 03:59:36 +00:00
Icon refactor
This commit is contained in:
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -14,8 +14,6 @@ namespace PortabilityLayer
|
||||
{
|
||||
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();
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
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));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
@@ -1526,6 +1593,28 @@ int ConvertSingleFile(const char *resPath, const PortabilityLayer::CombinedTimes
|
||||
contents.push_back(entry);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool isIcon = false;
|
||||
|
||||
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;
|
||||
|
||||
PlannedEntry entry;
|
||||
char resName[256];
|
||||
sprintf_s(resName, "%s/%i.bmp", resTag.m_id, static_cast<int>(res.m_resID));
|
||||
|
||||
entry.m_name = resName;
|
||||
|
||||
if (ImportIcon(entry.m_uncompressedContents, resData, resSize, iconSpec.m_width, iconSpec.m_height, iconSpec.m_bpp))
|
||||
contents.push_back(entry);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isIcon)
|
||||
{
|
||||
PlannedEntry entry;
|
||||
|
||||
@@ -1541,6 +1630,7 @@ int ConvertSingleFile(const char *resPath, const PortabilityLayer::CombinedTimes
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (havePatchFile)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user