diff --git a/Documentation/leveleditor.txt b/Documentation/leveleditor.txt index 5fcf1c9..8ee1e31 100644 --- a/Documentation/leveleditor.txt +++ b/Documentation/leveleditor.txt @@ -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. \ No newline at end of file + +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. \ No newline at end of file diff --git a/Documentation/userhouses.txt b/Documentation/userhouses.txt index b73cff1..278ec7e 100644 --- a/Documentation/userhouses.txt +++ b/Documentation/userhouses.txt @@ -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. \ No newline at end of file +backgrounds, audio triggers, and icons may load from the house data. diff --git a/FTagData/FTagData.cpp b/FTagData/FTagData.cpp index 705a75d..c0337df 100644 --- a/FTagData/FTagData.cpp +++ b/FTagData/FTagData.cpp @@ -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; diff --git a/GpApp/HouseIO.cpp b/GpApp/HouseIO.cpp index b877b3f..1714bc8 100644 --- a/GpApp/HouseIO.cpp +++ b/GpApp/HouseIO.cpp @@ -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 &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 &resData, const THand return result; } + +static ExportHouseResult_t TryExportIcon(GpVector &resData, const THandle &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(); + + if (bmp->GetRect() != rect) + return ExportHouseResults::kResourceError; + + if (NewGWorld(&surface, GpPixelFormats::kRGB32, rect) != PLErrors::kNone) + return ExportHouseResults::kMemError; + + surface->DrawPicture(resHandle.StaticCast(), 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(static_cast(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(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 &resData, PortabilityLayer::IResourceArchive *resArchive, const PortabilityLayer::ResTypeID &resTypeID, int16_t resID) { THandle resHandle = resArchive->LoadResource(resTypeID, resID); @@ -3224,6 +3308,35 @@ static ExportHouseResult_t TryExportResource(GpVector &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 &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(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(); diff --git a/GpApp/Utilities.cpp b/GpApp/Utilities.cpp index d648936..868bea9 100644 --- a/GpApp/Utilities.cpp +++ b/GpApp/Utilities.cpp @@ -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 img = PortabilityLayer::IconLoader::GetInstance()->LoadSimpleColorIcon(hdl); - + THandle img = hdl.StaticCast(); 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 img = PortabilityLayer::IconLoader::GetInstance()->LoadBWIcon(hdl); - + THandle img = hdl.StaticCast(); 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(); diff --git a/PortabilityLayer/IconLoader.cpp b/PortabilityLayer/IconLoader.cpp index 1583819..12820a5 100644 --- a/PortabilityLayer/IconLoader.cpp +++ b/PortabilityLayer/IconLoader.cpp @@ -44,8 +44,6 @@ namespace PortabilityLayer { public: bool LoadColorIcon(const int16_t id, THandle &outColorImage, THandle &outBWImage, THandle &outMaskImage) override; - THandle LoadSimpleColorIcon(const THandle &hdl) override; - THandle LoadBWIcon(const THandle &hdl) override; static IconLoaderImpl *GetInstance(); @@ -317,87 +315,6 @@ namespace PortabilityLayer return true; } - THandle IconLoaderImpl::LoadSimpleColorIcon(const THandle &hdl) - { - if (hdl == nullptr || hdl.MMBlock()->m_size != 1024) - return THandle(); - - const Rect rect = Rect::Create(0, 0, 32, 32); - - GpPixelFormat_t pixelFormat = DisplayDeviceManager::GetInstance()->GetPixelFormat(); - - THandle pixMap = PixMapImpl::Create(rect, pixelFormat); - if (!pixMap) - return THandle(); - - const uint8_t *inData = static_cast(*hdl); - uint8_t *outData = static_cast((*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(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 IconLoaderImpl::LoadBWIcon(const THandle &hdl) - { - if (hdl == nullptr || hdl.MMBlock()->m_size != 128) - return THandle(); - - const Rect rect = Rect::Create(0, 0, 32, 32); - THandle pixMap = PixMapImpl::Create(rect, GpPixelFormats::kBW1); - if (!pixMap) - return THandle(); - - const uint8_t *inData = static_cast(*hdl); - uint8_t *outData = static_cast((*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; diff --git a/PortabilityLayer/IconLoader.h b/PortabilityLayer/IconLoader.h index c1ffdc7..3528d47 100644 --- a/PortabilityLayer/IconLoader.h +++ b/PortabilityLayer/IconLoader.h @@ -9,13 +9,11 @@ namespace PortabilityLayer { class PixMapImpl; class SimpleImage; - + class IconLoader { public: virtual bool LoadColorIcon(const int16_t id, THandle &outColorImage, THandle &outBWImage, THandle &outMaskImage) = 0; - virtual THandle LoadSimpleColorIcon(const THandle &hdl) = 0; - virtual THandle LoadBWIcon(const THandle &hdl) = 0; static IconLoader *GetInstance(); }; diff --git a/PortabilityLayer/MacFileInfo.h b/PortabilityLayer/MacFileInfo.h index 4db98fb..4254a24 100644 --- a/PortabilityLayer/MacFileInfo.h +++ b/PortabilityLayer/MacFileInfo.h @@ -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 diff --git a/PortabilityLayer/MenuManager.cpp b/PortabilityLayer/MenuManager.cpp index 2a59969..e55e6f7 100644 --- a/PortabilityLayer/MenuManager.cpp +++ b/PortabilityLayer/MenuManager.cpp @@ -892,13 +892,41 @@ namespace PortabilityLayer void *storage = static_cast(NewPtr(sizeof(GraphicType_t))); if (storage) { - memcpy(m_iconMask, static_cast(*icsHandle) + 32, 32); - memcpy(m_iconColors, static_cast(*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(), Rect::Create(-16, 0, 16, 16)); + iconColorTemp->DrawPicture(ics8Handle.StaticCast(), 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(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(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; } diff --git a/PortabilityLayer/PLMovies.cpp b/PortabilityLayer/PLMovies.cpp index f03aa71..72d8b8d 100644 --- a/PortabilityLayer/PLMovies.cpp +++ b/PortabilityLayer/PLMovies.cpp @@ -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); diff --git a/PortabilityLayer/PLQDOffscreen.cpp b/PortabilityLayer/PLQDOffscreen.cpp index 0a80c2a..ce1ba19 100644 --- a/PortabilityLayer/PLQDOffscreen.cpp +++ b/PortabilityLayer/PLQDOffscreen.cpp @@ -18,9 +18,9 @@ #include #include -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) diff --git a/PortabilityLayer/PLQDOffscreen.h b/PortabilityLayer/PLQDOffscreen.h index 28e935a..970fab7 100644 --- a/PortabilityLayer/PLQDOffscreen.h +++ b/PortabilityLayer/PLQDOffscreen.h @@ -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); diff --git a/PortabilityLayer/PLQDraw.cpp b/PortabilityLayer/PLQDraw.cpp index 2a3199c..5fc7c3e 100644 --- a/PortabilityLayer/PLQDraw.cpp +++ b/PortabilityLayer/PLQDraw.cpp @@ -584,7 +584,7 @@ void DrawSurface::DrawPicture(THandle 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); diff --git a/PortabilityLayer/PLResourceManager.cpp b/PortabilityLayer/PLResourceManager.cpp index 71ff27a..c8019ed 100644 --- a/PortabilityLayer/PLResourceManager.cpp +++ b/PortabilityLayer/PLResourceManager.cpp @@ -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; diff --git a/PortabilityLayer/QDManager.cpp b/PortabilityLayer/QDManager.cpp index c8a0288..1b8ef84 100644 --- a/PortabilityLayer/QDManager.cpp +++ b/PortabilityLayer/QDManager.cpp @@ -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) diff --git a/PortabilityLayer/QDManager.h b/PortabilityLayer/QDManager.h index af08850..e6c687d 100644 --- a/PortabilityLayer/QDManager.h +++ b/PortabilityLayer/QDManager.h @@ -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(); diff --git a/PortabilityLayer/QDStandardPalette.cpp b/PortabilityLayer/QDStandardPalette.cpp index c39098e..d14448f 100644 --- a/PortabilityLayer/QDStandardPalette.cpp +++ b/PortabilityLayer/QDStandardPalette.cpp @@ -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; } diff --git a/PortabilityLayer/QDStandardPalette.h b/PortabilityLayer/QDStandardPalette.h index d43df32..b0ba753 100644 --- a/PortabilityLayer/QDStandardPalette.h +++ b/PortabilityLayer/QDStandardPalette.h @@ -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]; + }; } diff --git a/gpr2gpa/gpr2gpa.cpp b/gpr2gpa/gpr2gpa.cpp index 366e936..c4c4830 100644 --- a/gpr2gpa/gpr2gpa.cpp +++ b/gpr2gpa/gpr2gpa.cpp @@ -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 &entries, const P fclose(outF); } + + +bool ExportBMP(size_t width, size_t height, size_t pitchInElements, const PortabilityLayer::RGBAColor *pixelData, std::vector &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(imageFileSize); + fileHeader.m_imageDataStart = static_cast(fileHeaderSize + infoHeaderSize + ctabSize + postCTabPaddingSize); + fileHeader.m_reserved1 = 0; + fileHeader.m_reserved2 = 0; + + VectorAppend(outData, reinterpret_cast(&fileHeader), sizeof(fileHeader)); + + PortabilityLayer::BitmapInfoHeader infoHeader; + infoHeader.m_thisStructureSize = sizeof(infoHeader); + infoHeader.m_width = static_cast(width); + infoHeader.m_height = static_cast(height); + infoHeader.m_planes = 1; + infoHeader.m_bitsPerPixel = bpp; + infoHeader.m_compression = PortabilityLayer::BitmapConstants::kCompressionRGB; + infoHeader.m_imageSize = static_cast(imageDataSize); + infoHeader.m_xPixelsPerMeter = 2835; + infoHeader.m_yPixelsPerMeter = 2835; + infoHeader.m_numColors = (bpp < 16) ? numColors : 0; + infoHeader.m_importantColorCount = 0; + + VectorAppend(outData, reinterpret_cast(&infoHeader), sizeof(infoHeader)); + + if (bpp < 16) + VectorAppend(outData, reinterpret_cast(colorTable), sizeof(PortabilityLayer::BitmapColorTableEntry) * numColors); + + for (size_t i = 0; i < postCTabPaddingSize; i++) + outData.push_back(0); + + std::vector 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(packedValue & 0xff); + rowPackData[col * 2 + 1] = static_cast((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 &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(imageFileSize); - fileHeader.m_imageDataStart = static_cast(fileHeaderSize + infoHeaderSize + ctabSize + postCTabPaddingSize); - fileHeader.m_reserved1 = 0; - fileHeader.m_reserved2 = 0; - - VectorAppend(outData, reinterpret_cast(&fileHeader), sizeof(fileHeader)); - - PortabilityLayer::BitmapInfoHeader infoHeader; - infoHeader.m_thisStructureSize = sizeof(infoHeader); - infoHeader.m_width = static_cast(width); - infoHeader.m_height = static_cast(height); - infoHeader.m_planes = 1; - infoHeader.m_bitsPerPixel = bpp; - infoHeader.m_compression = PortabilityLayer::BitmapConstants::kCompressionRGB; - infoHeader.m_imageSize = static_cast(imageDataSize); - infoHeader.m_xPixelsPerMeter = 2835; - infoHeader.m_yPixelsPerMeter = 2835; - infoHeader.m_numColors = (bpp < 16) ? numColors : 0; - infoHeader.m_importantColorCount = 0; - - VectorAppend(outData, reinterpret_cast(&infoHeader), sizeof(infoHeader)); - - if (bpp < 16) - VectorAppend(outData, reinterpret_cast(colorTable), sizeof(PortabilityLayer::BitmapColorTableEntry) * numColors); - - for (size_t i = 0; i < postCTabPaddingSize; i++) - outData.push_back(0); - - std::vector 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(packedValue & 0xff); - rowPackData[col * 2 + 1] = static_cast((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 &outBMP, const void *inData, size_t inSize) @@ -1266,6 +1270,51 @@ bool ImportDialogItemTemplate(std::vector &outTXT, const void *inData, return true; } +bool ImportIcon(std::vector &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 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(inData)[byteOffset]; + value = (value >> bitOffset) & mask; + pixelData[i] = palette[value]; + } + + return ExportBMP(width, height, width, &pixelData[0], outBMP); +} + void ReadFileToVector(FILE *f, std::vector &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(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(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(res.m_resID)); + + entry.m_name = resName; + entry.m_uncompressedContents.resize(res.GetSize()); + + memcpy(&entry.m_uncompressedContents[0], resData, resSize); + + contents.push_back(entry); + } } } }