From 50872dfc14475a4da1af1a7ec5bfaac20fd9be84 Mon Sep 17 00:00:00 2001 From: Adam Heinermann Date: Tue, 23 Sep 2025 00:08:41 -0700 Subject: [PATCH] feat(trans): add STrans functions --- .gitignore | 1 + storm/Core.cpp | 3 +- storm/Transparency.cpp | 804 ++++++++++++++++++ storm/Transparency.hpp | 51 ++ test/CMakeLists.txt | 2 + test/Transparency.cpp | 1559 ++++++++++++++++++++++++++++++++++ test/stormdll/storm.def | 28 +- test/stormdll/stormstubs.cpp | 18 + test/trans/TransInternal.cpp | 120 +++ 9 files changed, 2571 insertions(+), 15 deletions(-) create mode 100644 storm/Transparency.cpp create mode 100644 storm/Transparency.hpp create mode 100644 test/Transparency.cpp create mode 100644 test/trans/TransInternal.cpp diff --git a/.gitignore b/.gitignore index f9ee148..b46d6cb 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ CMakeSettings.json .vs/ /build +/build_* /cmake-build-* /dist /out diff --git a/storm/Core.cpp b/storm/Core.cpp index e181c84..05d6dd8 100644 --- a/storm/Core.cpp +++ b/storm/Core.cpp @@ -1,5 +1,6 @@ #include "Core.hpp" #include "Event.hpp" +#include "Transparency.hpp" int32_t STORMAPI StormDestroy() { // Combined list of all calls from every game @@ -22,6 +23,6 @@ int32_t STORMAPI StormDestroy() { // SRegDestroy(); // SErrDestroy(); // SLogDestroy(); - // STransDestroy(); + STransDestroy(); return 1; } diff --git a/storm/Transparency.cpp b/storm/Transparency.cpp new file mode 100644 index 0000000..649720f --- /dev/null +++ b/storm/Transparency.cpp @@ -0,0 +1,804 @@ +#include "storm/Transparency.hpp" +#include "storm/List.hpp" +#include "storm/Error.hpp" + +#include +#include +#include + +#define COPY 0 +#define SKIP 1 +#define NUM_OPS 2 + +#define MAXSPANLENGTH 0xFC +#define TRANS_CHUNKSIZE 4096 + +struct TRANS : TSLinkedNode { + uint8_t* data; + uint32_t dataalloc; + uint32_t databytes; + uint32_t instructionoffset; + int32_t width; + int32_t height; + RECT boundrect; +}; + +// Replacing windows struct +struct WHOA_SIZE { + int32_t cx, cy; +}; + +struct BUFFER { + uint8_t* data; + uint32_t bytesalloc; + uint32_t bytesused; + uint32_t chunksize; +}; + +// Defaults from SC 1.17's storm.dll +WHOA_SIZE s_dirtysize = { 40, 30 }; +int32_t s_dirtyxshift = 4; +int32_t s_dirtyxsize = 16; +int32_t s_dirtyyshift = 4; +int32_t s_dirtyysize = 16; + +uint32_t* s_dirtyoffset; +uint8_t* s_savedata; +uint32_t s_savedataalloc; +STORM_LIST(TRANS) s_translist; + +void BufferCreate(BUFFER* buffer) { + buffer->chunksize = TRANS_CHUNKSIZE; + buffer->bytesused = 0; + if (s_savedata) { + buffer->data = s_savedata; + buffer->bytesalloc = s_savedataalloc; + s_savedata = nullptr; + s_savedataalloc = 0; + } + else { + buffer->bytesalloc = TRANS_CHUNKSIZE; + buffer->data = static_cast(STORM_ALLOC(buffer->bytesalloc)); + } +} + +void BufferReserve(BUFFER* buffer, uint32_t bytes, uint8_t** adjptr1, uint8_t** adjptr2) { + uint32_t newalloc = buffer->bytesalloc; + while (newalloc < bytes + buffer->bytesused) { + newalloc += buffer->chunksize; + } + + if (newalloc != buffer->bytesalloc) { + uint8_t* newdata = static_cast(STORM_ALLOC(newalloc)); + SMemCopy(newdata, buffer->data, buffer->bytesused); + STORM_FREE(buffer->data); + + if (adjptr1 && *adjptr1) { + *adjptr1 = &newdata[*adjptr1 - buffer->data]; + } + if (adjptr2 && *adjptr2) { + *adjptr2 = &newdata[*adjptr2 - buffer->data]; + } + buffer->bytesalloc = newalloc; + buffer->data = newdata; + } +} + +void ConvertBitmapToTransparency(uint8_t* bits, int32_t width, int32_t height, int32_t bitdepth, RECT* rect, RECT* boundrect, uint8_t colorkey, int32_t maskonly, uint8_t* data, uint32_t* databytes, uint32_t* instructionoffset) { + uint32_t size = 0; + int32_t start = rect ? rect->left + width * rect->top : 0; + int32_t cx = rect ? rect->right - rect->left : width; + int32_t cy = rect ? rect->bottom - rect->top : height; + + int32_t adjust = width - cx; + + *boundrect = { INT_MAX, INT_MAX, 0, 0 }; + + if (!maskonly) { + uint8_t* source = &bits[start]; + for (int32_t y = 0; y < cy; y++) { + bool found = false; + for (int32_t x = 0; x < cx; x++) { + if (*source != colorkey) { + size++; + if (data) *data++ = *source; + + if (x < boundrect->left) boundrect->left = x; + if (x >= boundrect->right) boundrect->right = x + 1; + found = true; + } + source++; + } + + if (found) { + if (y < boundrect->top) boundrect->top = y; + if (y >= boundrect->bottom) boundrect->bottom = y + 1; + } + + source += adjust; + } + } + + if (boundrect->left > boundrect->right) boundrect->left = boundrect->right; + if (boundrect->top > boundrect->bottom) boundrect->top = boundrect->bottom; + + if (size % 4 != 0) { + if (data) data += 4 - (size % 4); + size += 4 - (size % 4); + } + + if (instructionoffset) *instructionoffset = size; + + uint8_t* source = &bits[start]; + while (cy--) { + uint8_t copybytes = 0; + uint8_t skipbytes = 0; + bool copymode = true; + + for (int32_t x = cx; x--;) { + bool output = false; + bool expectedcopymode = *source++ != colorkey; + if (expectedcopymode == copymode) { + if (copymode) { + copybytes++; + } + else { + skipbytes++; + } + } + else if (copymode) { + copymode = false; + skipbytes++; + } + else { + output = true; + source--; + x++; + } + + if (output || copybytes == MAXSPANLENGTH || skipbytes == MAXSPANLENGTH || x == 0) { + size += 2; + if (data) { + *data++ = copybytes; + *data++ = skipbytes; + } + copybytes = 0; + skipbytes = 0; + copymode = true; + } + } + + size += 2; + if (data) { + *data++ = 0; + *data++ = 0; + } + + source += adjust; + } + + if (databytes) *databytes = size; +} + +int32_t ConvertColorRefToColorData(uint32_t colorref) { + if (colorref & STRANS_COLORKEY_VALID) { + return colorref & STRANS_COLORKEY_MASK; + } + return 0; +} + +HSTRANS CreateTransparencyRecord(HSTRANS baseptr) { + HSTRANS transptr = s_translist.NewNode(STORM_LIST_TAIL, 0, 0); + if (baseptr) { + transptr->dataalloc = baseptr->dataalloc; + transptr->databytes = baseptr->databytes; + transptr->instructionoffset = baseptr->instructionoffset; + transptr->width = baseptr->width; + transptr->height = baseptr->height; + transptr->boundrect = baseptr->boundrect; + } + return transptr; +} + +int32_t DetermineShift(int32_t value, int32_t* shift) { + int32_t bits = 0, curr = 1; + while (curr < value) { + bits++; + curr <<= 1; + } + + *shift = bits; + return curr == value; +} + +int32_t InternalCreateTransparency(uint8_t* bits, int32_t width, int32_t height, int32_t bitdepth, RECT* rect, uint32_t colorkey, int32_t maskonly, HSTRANS* handle) { + if (bitdepth != 8) return 0; + + uint8_t paletteindex = ConvertColorRefToColorData(colorkey); + uint32_t transbytes = 0; + uint32_t instructionoffset = 0; + RECT boundrect; + + ConvertBitmapToTransparency(bits, width, height, bitdepth, rect, &boundrect, paletteindex, maskonly, nullptr, &transbytes, &instructionoffset); + + uint8_t* transdata = static_cast(STORM_ALLOC(transbytes)); + ConvertBitmapToTransparency(bits, width, height, bitdepth, rect, &boundrect, paletteindex, maskonly, transdata, nullptr, nullptr); + + HSTRANS newptr = CreateTransparencyRecord(nullptr); + newptr->boundrect = boundrect; + newptr->data = transdata; + newptr->dataalloc = transbytes; + newptr->databytes = transbytes; + newptr->instructionoffset = instructionoffset; + newptr->width = rect ? rect->right - rect->left : width; + newptr->height = rect ? rect->bottom - rect->top : height; + + *handle = newptr; + return 1; +} + +void InternalDrawTransparency(HSTRANS trans, uint8_t* dest, int32_t destadjust) { + uint8_t* sourceinst = &trans->data[trans->instructionoffset]; + uint8_t* sourcedata = trans->data; + int32_t cy = trans->height; + while (cy--) { + uint8_t copybytes = *sourceinst++; + uint8_t skipbytes = *sourceinst++; + while (copybytes || skipbytes) { + while(copybytes) { + *dest++ = *sourcedata++; + copybytes--; + } + dest += skipbytes; + + copybytes = *sourceinst++; + skipbytes = *sourceinst++; + } + dest += destadjust; + } +} + +void InternalDrawTransparencyFromSource(HSTRANS trans, uint8_t* dest, uint8_t* source, int32_t destadjust, int32_t sourceadjust) { + uint8_t* sourceinst = &trans->data[trans->instructionoffset]; + for (int32_t y = 0; y < trans->height; y++) { + uint8_t copybytes = *(sourceinst++); + uint8_t skipbytes = *(sourceinst++); + while (copybytes || skipbytes) { + std::memcpy(dest, source, copybytes); + source += copybytes; + dest += copybytes; + + dest += skipbytes; + source += skipbytes; + + copybytes = *(sourceinst++); + skipbytes = *(sourceinst++); + } + dest += destadjust; + source += sourceadjust; + } +} + +int32_t STORMAPI STransBlt(uint8_t* dest, int32_t destx, int32_t desty, int32_t destpitch, HSTRANS transparency) { + STORM_ASSERT(dest); + STORM_ASSERT(destpitch > 0); + STORM_ASSERT(transparency); + STORM_ASSERT(transparency->instructionoffset); + + InternalDrawTransparency(transparency, &dest[destpitch * desty + destx], destpitch - transparency->width); + return 1; +} + +int32_t STORMAPI STransBltUsingMask(uint8_t* dest, uint8_t* source, int32_t destpitch, int32_t sourcepitch, HSTRANS mask) { + STORM_ASSERT(dest); + STORM_ASSERT(source); + STORM_ASSERT(destpitch > 0); + STORM_ASSERT(mask); + + int32_t sourceadjust = sourcepitch ? sourcepitch - mask->width : 0; + int32_t destadjust = destpitch - mask->width; + InternalDrawTransparencyFromSource(mask, dest, source, destadjust, sourceadjust); + return 1; +} + +int32_t STORMAPI STransCombineMasks(HSTRANS basemask, HSTRANS secondmask, int32_t offsetx, int32_t offsety, uint32_t flags, HSTRANS* handle) { + if (handle) *handle = nullptr; + + STORM_VALIDATE_BEGIN; + STORM_VALIDATE(basemask); + STORM_VALIDATE(secondmask); + STORM_VALIDATE(handle); + STORM_VALIDATE_END; + + bool intersect = (flags & STRANS_CF_INTERSECT) != 0; + bool invertsecond = (flags & STRANS_CF_INVERTSECOND) != 0; + + bool usespan[NUM_OPS][NUM_OPS][NUM_OPS]; + for (int32_t spantype0 = 0; spantype0 < NUM_OPS; spantype0++) { + for (int32_t spantypedest = 0; spantypedest < NUM_OPS; spantypedest++) { + if (intersect) { + usespan[spantype0][spantypedest][invertsecond ? SKIP : COPY] = (spantype0 == spantypedest); + usespan[spantype0][spantypedest][invertsecond ? COPY : SKIP] = (spantypedest == SKIP); + } + else { + usespan[spantype0][spantypedest][invertsecond ? SKIP : COPY] = (spantypedest == COPY); + usespan[spantype0][spantypedest][invertsecond ? COPY : SKIP] = (spantype0 == spantypedest); + } + } + } + + uint8_t* baseptr = &basemask->data[basemask->instructionoffset]; + uint8_t* secondptr = &secondmask->data[secondmask->instructionoffset]; + + uint8_t copybytes, skipbytes; + + for (int32_t i = offsety; i < 0; i++) { + do { + copybytes = *secondptr++; + skipbytes = *secondptr++; + } while (copybytes || skipbytes); + } + + BUFFER buffer; + BufferCreate(&buffer); + uint8_t* dest = buffer.data; + for (int32_t line = 0; line < basemask->height; line++) { + BufferReserve(&buffer, 2 * (basemask->width + 1), &dest, nullptr); + + if (line >= offsety && line <= offsety + secondmask->height - 1) { + int32_t span[2][NUM_OPS] = {}; + span[1][SKIP] = std::max(0, offsetx); + bool hitend = false; + + if (offsetx < 0) { + int32_t bytesleft = -offsetx; + while (bytesleft) { + span[1][COPY] = *secondptr++; + span[1][SKIP] = *secondptr++; + + if (span[1][COPY] == 0 && span[1][SKIP] == 0) { + span[1][SKIP] = INT_MAX; + hitend = true; + break; + } + + for (int32_t adjustremaining = 0; adjustremaining < NUM_OPS; adjustremaining++) { + int32_t adjustment = std::min(bytesleft, span[1][adjustremaining]); + span[1][adjustremaining] -= adjustment; + bytesleft -= adjustment; + } + } + } + + while(1) { + span[0][COPY] = *baseptr++; + span[0][SKIP] = *baseptr++; + if (span[0][COPY] == 0 && span[0][SKIP] == 0) break; + + for (int32_t j = 0; j < NUM_OPS; j++) { + while(span[0][j]) { + if (span[1][COPY] == 0 && span[1][SKIP] == 0) { + span[1][COPY] = *secondptr++; + span[1][SKIP] = *secondptr++; + if (span[1][COPY] == 0 && span[1][SKIP] == 0) { + span[1][SKIP] = INT_MAX; + hitend = true; + } + } + + uint8_t inst[NUM_OPS]; + for (int32_t k = 0; k < NUM_OPS; k++) { + int32_t spanlength = 0; + if (usespan[j][k][COPY]) { + spanlength = span[1][COPY]; + } + + if (usespan[j][k][SKIP] && (usespan[j][k][COPY] || !span[1][COPY])) { + spanlength += span[1][SKIP]; + } + + spanlength = std::min(span[0][j], spanlength); + inst[k] = spanlength; + span[0][j] -= spanlength; + for (int32_t m = 0; m < NUM_OPS; m++) { + int32_t v11 = std::min(span[1][m], spanlength); + span[1][m] -= v11; + spanlength -= v11; + } + } + + if (inst[COPY] || inst[SKIP]) { + *dest++ = inst[COPY]; + *dest++ = inst[SKIP]; + buffer.bytesused += 2; + } + } + } + } + + *dest++ = 0; + *dest++ = 0; + buffer.bytesused += 2; + + if (!hitend) { + do { + copybytes = *secondptr++; + skipbytes = *secondptr++; + } while (copybytes || skipbytes); + } + } + else if (intersect == invertsecond) { + do { + buffer.bytesused += 2; + copybytes = *baseptr++; + skipbytes = *baseptr++; + + *dest++ = copybytes; + *dest++ = skipbytes; + } while (copybytes || skipbytes); + } + else { + int32_t width = basemask->width; + while (width) { + int32_t skipspan = std::min(width, MAXSPANLENGTH); + *dest++ = 0; + *dest++ = skipspan; + buffer.bytesused += 2; + + width -= skipspan; + } + *dest++ = 0; + *dest++ = 0; + buffer.bytesused += 2; + + do { + copybytes = *baseptr++; + skipbytes = *baseptr++; + } while (copybytes || skipbytes); + } + } + + HSTRANS newptr = CreateTransparencyRecord(basemask); + newptr->data = buffer.data; + newptr->dataalloc = buffer.bytesalloc; + newptr->databytes = buffer.bytesused; + newptr->instructionoffset = 0; + *handle = newptr; + return 1; +} + +int32_t STORMAPI STransCreateE(uint8_t* bits, int32_t width, int32_t height, int32_t bitdepth, RECT* rect, uint32_t colorkey, HSTRANS* handle) { + if (handle) *handle = nullptr; + + STORM_VALIDATE_BEGIN; + STORM_VALIDATE(bits); + STORM_VALIDATE(width); + STORM_VALIDATE(height); + STORM_VALIDATE(handle); + STORM_VALIDATE_END; + + return InternalCreateTransparency(bits, width, height, bitdepth, rect, colorkey, 0, handle); +} + +int32_t STORMAPI STransCreateI(uint8_t* bits, int32_t width, int32_t height, int32_t bitdepth, RECT* rect, uint32_t colorkey, HSTRANS* handle) { + RECT exclrect; + RECT* exclrectptr = rect; + if (rect) { + exclrect = { rect->left, rect->top, rect->right + 1, rect->bottom + 1 }; + exclrectptr = &exclrect; + } + return STransCreateE(bits, width, height, bitdepth, exclrectptr, colorkey, handle); +} + +int32_t STORMAPI STransCreateMaskE(uint8_t* bits, int32_t width, int32_t height, int32_t bitdepth, RECT* rect, uint32_t colorkey, HSTRANS* handle) { + if (handle) *handle = nullptr; + + STORM_VALIDATE_BEGIN; + STORM_VALIDATE(bits); + STORM_VALIDATE(width); + STORM_VALIDATE(height); + STORM_VALIDATE(handle); + STORM_VALIDATE_END; + + return InternalCreateTransparency(bits, width, height, bitdepth, rect, colorkey, 1, handle); +} + +int32_t STORMAPI STransCreateMaskI(uint8_t* bits, int32_t width, int32_t height, int32_t bitdepth, RECT* rect, uint32_t colorkey, HSTRANS* handle) { + RECT exclrect; + RECT* exclrectptr = rect; + if (rect) { + exclrect = { rect->left, rect->top, rect->right + 1, rect->bottom + 1 }; + exclrectptr = &exclrect; + } + return STransCreateMaskE(bits, width, height, bitdepth, exclrectptr, colorkey, handle); +} + +int32_t STORMAPI STransDelete(HSTRANS handle) { + if (handle->data) { + if (s_savedata) { + STORM_FREE(s_savedata); + } + + s_savedata = handle->data; + s_savedataalloc = handle->dataalloc; + + handle->data = nullptr; + handle->dataalloc = 0; + } + + s_translist.DeleteNode(handle); + return 1; +} + +int32_t STORMAPI STransDestroy() { + while (TRANS* curr = s_translist.Head()) { + //SErrReportResourceLeak("HSTRANS"); + STransDelete(curr); + } + + if (s_dirtyoffset) { + STORM_FREE(s_dirtyoffset); + s_dirtyoffset = nullptr; + } + + if (s_savedata) { + STORM_FREE(s_savedata); + s_savedata = nullptr; + s_savedataalloc = 0; + } + return 1; +} + +int32_t STORMAPI STransDuplicate(HSTRANS source, HSTRANS* handle) { + if (handle) *handle = nullptr; + STORM_VALIDATE_BEGIN; + STORM_VALIDATE(source); + STORM_VALIDATE(handle); + STORM_VALIDATE_END; + + uint8_t* data = static_cast(STORM_ALLOC(source->dataalloc)); + SMemCopy(data, source->data, source->databytes); + + HSTRANS newtrans = CreateTransparencyRecord(source); + newtrans->data = data; + + *handle = newtrans; + return 1; +} + +int32_t STORMAPI STransIntersectDirtyArray(HSTRANS sourcemask, uint8_t* dirtyarray, uint8_t dirtyarraymask, HSTRANS* handle) { + if (handle) *handle = nullptr; + STORM_VALIDATE_BEGIN; + STORM_VALIDATE(sourcemask); + STORM_VALIDATE(dirtyarray); + STORM_VALIDATE(dirtyarraymask); + STORM_VALIDATE(handle); + STORM_VALIDATE_END; + + if (s_dirtyoffset == 0) return 0; + + BUFFER buffer; + BufferCreate(&buffer); + + uint8_t* source = &sourcemask->data[sourcemask->instructionoffset]; + uint8_t* dest = buffer.data; + uint8_t* lastsource = source; + uint8_t* lastdest = dest; + + for (int32_t y = 0; y < sourcemask->height; y++) { + BufferReserve(&buffer, 2 * (sourcemask->width + 1), &dest, &lastdest); + + // wtf? + if (((s_dirtyysize - 1) & y) != 0 && !memcmp(source, lastsource, source - lastsource)) { + ptrdiff_t sourcebytes = source - lastsource; + ptrdiff_t destbytes = dest - lastdest; + memcpy(dest, lastdest, dest - lastdest); + + lastsource = source; + lastdest = dest; + source += sourcebytes; + dest += destbytes; + buffer.bytesused += static_cast(destbytes); + } + else { + uint8_t copybytes, skipbytes; + + lastsource = source; + lastdest = dest; + uint8_t* dirty = &dirtyarray[s_dirtyoffset[y >> s_dirtyyshift]]; + uint32_t xoffset = 0; + do { + copybytes = *source++; + skipbytes = *source++; + + if (copybytes) { + uint8_t bytesleft = copybytes; + uint8_t length = 0; + bool copymode = true; + + while (bytesleft != 0 || length != 0) { + if (bytesleft != 0 && ((dirtyarraymask & *dirty) != 0) == copymode) { + uint8_t cellleft = s_dirtyxsize - xoffset; + if (bytesleft >= cellleft) { + length += cellleft; + bytesleft -= cellleft; + xoffset = 0; + dirty++; + } + else { + length += bytesleft; + xoffset += bytesleft; + bytesleft = 0; + } + } + else { + *dest++ = length; + buffer.bytesused++; + length = 0; + copymode = !copymode; + } + } + + if (skipbytes || !copymode) { + if (copymode) { + *dest++ = 0; + buffer.bytesused++; + } + *dest++ = skipbytes; + buffer.bytesused++; + } + } + else { + *dest++ = 0; + *dest++ = skipbytes; + buffer.bytesused += 2; + } + + if (skipbytes) { + xoffset += skipbytes; + dirty += xoffset >> s_dirtyxshift; + xoffset &= s_dirtyxsize - 1; + } + + } while (copybytes || skipbytes); + } + } + + HSTRANS newptr = CreateTransparencyRecord(sourcemask); + newptr->data = buffer.data; + newptr->dataalloc = buffer.bytesalloc; + newptr->databytes = buffer.bytesused; + newptr->instructionoffset = 0; + *handle = newptr; + return 1; +} + +int32_t STORMAPI STransInvertMask(HSTRANS sourcemask, HSTRANS* handle) { + if (handle) *handle = nullptr; + STORM_VALIDATE_BEGIN; + STORM_VALIDATE(sourcemask); + STORM_VALIDATE(handle); + STORM_VALIDATE_END; + + uint32_t dataalloc = sourcemask->databytes - sourcemask->instructionoffset + 2 * sourcemask->height; + uint8_t* data = static_cast(STORM_ALLOC(dataalloc)); + uint32_t bytes = 0; + + uint8_t* source = &sourcemask->data[sourcemask->instructionoffset]; + uint8_t* dest = data; + + for (int32_t cy = sourcemask->height; cy--;) { + uint8_t copybytes = 0, skipbytes = 0; + do { + skipbytes = *source++; + if (copybytes || skipbytes) { + *dest++ = copybytes; + *dest++ = skipbytes; + bytes += 2; + } + copybytes = *source++; + } while (copybytes || skipbytes); + + *dest++ = 0; + *dest++ = 0; + bytes += 2; + } + + HSTRANS newptr = CreateTransparencyRecord(sourcemask); + newptr->data = data; + newptr->dataalloc = dataalloc; + newptr->databytes = bytes; + newptr->instructionoffset = 0; + + *handle = newptr; + return 1; +} + +int32_t STORMAPI STransIsPixelInMask(HSTRANS mask, int32_t offsetx, int32_t offsety) { + STORM_VALIDATE_BEGIN; + STORM_VALIDATE(mask); + STORM_VALIDATE_END; + + if (offsetx < 0 || offsety < 0 || offsetx >= mask->width || offsety >= mask->height) { + return 0; + } + + uint8_t copybytes, skipbytes; + uint8_t* instptr = &mask->data[mask->instructionoffset]; + while(offsety--) { + do { + copybytes = *instptr++; + skipbytes = *instptr++; + } while (copybytes || skipbytes); + } + + copybytes = *instptr++; + skipbytes = *instptr++; + while(copybytes || skipbytes) { + if (copybytes > offsetx) return 1; + offsetx -= copybytes; + + if (skipbytes > offsetx) return 0; + offsetx -= skipbytes; + + copybytes = *instptr++; + skipbytes = *instptr++; + } + return 0; +} + +int32_t STORMAPI STransSetDirtyArrayInfo(int32_t screencx, int32_t screency, int32_t cellcx, int32_t cellcy) { + if (s_dirtyoffset) { + STORM_FREE(s_dirtyoffset); + s_dirtyoffset = nullptr; + } + + if (!DetermineShift(cellcx, &s_dirtyxshift)) { + return 0; + } + + if (!DetermineShift(cellcy, &s_dirtyyshift)) { + return 0; + } + + s_dirtysize = { + (screencx + (1 << s_dirtyxshift) - 1) >> s_dirtyxshift, + (screency + (1 << s_dirtyyshift) - 1) >> s_dirtyyshift, + }; + + s_dirtyxsize = cellcx; + s_dirtyysize = cellcy; + + s_dirtyoffset = static_cast(STORM_ALLOC(sizeof(s_dirtyoffset[0]) * s_dirtysize.cy)); + + uint32_t offset = 0; + for (int32_t i = 0; i < s_dirtysize.cy; i++) { + s_dirtyoffset[i] = offset; + offset += s_dirtysize.cx; + } + return 1; +} + +int32_t STORMAPI STransUpdateDirtyArray(uint8_t* dirtyarray, uint8_t dirtyvalue, int32_t destx, int32_t desty, HSTRANS transparency, int32_t tracecontour) { + STORM_VALIDATE_BEGIN; + STORM_VALIDATE(dirtyarray); + STORM_VALIDATE(dirtyvalue); + STORM_VALIDATE(transparency); + STORM_VALIDATE_END; + + if (!s_dirtyoffset) return 0; + if (transparency->width <= 0 || transparency->height <= 0) return 0; + if (tracecontour) return 0; + + int32_t lastx = (transparency->boundrect.right + destx) >> s_dirtyxshift; + int32_t lasty = (transparency->boundrect.bottom + desty) >> s_dirtyyshift; + int32_t firstx = (transparency->boundrect.left + destx) >> s_dirtyxshift; + int32_t firsty = (transparency->boundrect.top + desty) >> s_dirtyyshift; + + for (int32_t y = firsty; y < lasty; y++) { + for (int32_t x = firstx; x < lastx; x++) { + dirtyarray[s_dirtyoffset[y] + x] |= dirtyvalue; + } + } + return 1; +} diff --git a/storm/Transparency.hpp b/storm/Transparency.hpp new file mode 100644 index 0000000..e7e5cf4 --- /dev/null +++ b/storm/Transparency.hpp @@ -0,0 +1,51 @@ +#ifndef STORM_TRANSPARENCY_HPP +#define STORM_TRANSPARENCY_HPP + +#include +#include "storm/Core.hpp" +#include "storm/region/Types.hpp" + +#define STRANS_CF_INTERSECT 1 +#define STRANS_CF_INVERTSECOND 2 +#define STRANS_CF_SUBTRACT 3 + +#define STRANS_COLORKEY_VALID 0x1000000 +#define STRANS_COLORKEY_MASK 0xFFFFFF +#define STRANS_COLORKEY(x) (((x) & STRANS_COLORKEY_MASK) | STRANS_COLORKEY_VALID) + +struct TRANS; +typedef TRANS* HSTRANS; + +int32_t STORMAPI STransBlt(uint8_t* dest, int32_t destx, int32_t desty, int32_t destpitch, HSTRANS transparency); + +int32_t STORMAPI STransBltUsingMask(uint8_t* dest, uint8_t* source, int32_t destpitch, int32_t sourcepitch, HSTRANS mask); + +int32_t STORMAPI STransCombineMasks(HSTRANS basemask, HSTRANS secondmask, int32_t offsetx, int32_t offsety, uint32_t flags, HSTRANS* handle); + +// Exclusive rect - [left,right), [top,bottom) +int32_t STORMAPI STransCreateE(uint8_t* bits, int32_t width, int32_t height, int32_t bitdepth, RECT* rect, uint32_t colorkey, HSTRANS* handle); + +// Inclusive rect - [left,right], [top,bottom] +int32_t STORMAPI STransCreateI(uint8_t* bits, int32_t width, int32_t height, int32_t bitdepth, RECT* rect, uint32_t colorkey, HSTRANS* handle); + +int32_t STORMAPI STransCreateMaskE(uint8_t* bits, int32_t width, int32_t height, int32_t bitdepth, RECT* rect, uint32_t colorkey, HSTRANS* handle); + +int32_t STORMAPI STransCreateMaskI(uint8_t* bits, int32_t width, int32_t height, int32_t bitdepth, RECT* rect, uint32_t colorkey, HSTRANS* handle); + +int32_t STORMAPI STransDelete(HSTRANS handle); + +int32_t STORMAPI STransDestroy(); + +int32_t STORMAPI STransDuplicate(HSTRANS source, HSTRANS* handle); + +int32_t STORMAPI STransIntersectDirtyArray(HSTRANS sourcemask, uint8_t* dirtyarray, uint8_t dirtyarraymask, HSTRANS* handle); + +int32_t STORMAPI STransInvertMask(HSTRANS sourcemask, HSTRANS* handle); + +int32_t STORMAPI STransIsPixelInMask(HSTRANS mask, int32_t offsetx, int32_t offsety); + +int32_t STORMAPI STransSetDirtyArrayInfo(int32_t screencx, int32_t screency, int32_t cellcx, int32_t cellcy); + +int32_t STORMAPI STransUpdateDirtyArray(uint8_t* dirtyarray, uint8_t dirtyvalue, int32_t destx, int32_t desty, HSTRANS transparency, int32_t tracecontour = 0); + +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b11814a..351b648 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -10,6 +10,7 @@ if(WHOA_TEST_STORMDLL) Region.cpp String.cpp Test.cpp + Transparency.cpp ) if(WHOA_STORMDLL_VERSION GREATER_EQUAL 2003) @@ -19,6 +20,7 @@ else() file(GLOB TEST_SOURCES "*.cpp" "big/*.cpp" + "trans/*.cpp" ) endif() diff --git a/test/Transparency.cpp b/test/Transparency.cpp new file mode 100644 index 0000000..0ff4e38 --- /dev/null +++ b/test/Transparency.cpp @@ -0,0 +1,1559 @@ +#include "test/Test.hpp" +#include "storm/Transparency.hpp" + +#include +#include +#include +#include + +const uint8_t OriginalImageBits[] = { +"...................................................................................................." +"......;&&&&&&&&&&;............................;&&&&&&&&&&+.........................................." +".......xxxx+..:x:xXXXXXXX:..;XXx....;XXXXXx:.+&&&;....::;XXXXXx:....:xXX;..:XXXXXXXX:XXXXXXX;;+:...." +"........+&&&&:.:&&&xxXxxxx:;+X&&$:+xxxxxx&&&+$&&&:...:+xxXxxx&&&+..;+x&&&:+xxXxxxx;:;+xxx+&&&:......" +".........:$&&&x....;&&&+..x&&;x&&;.x&&$.:&&$:$&&&:.....x&&$.:&&$:.;&&+;&&$.:&&&xxx...:&&&x.........." +"...........+&&&&:..;&&&+.;&&&&$&&&:x&&$x&&X..x&&&x.....x&&$x&&X...$&&&&$&&x:&&&x:....:&&&x.........." +".......x$;..:$&&&+.:&&&::$&&...;&&$x&&$.x&&&x.+&&&$:...x&&$.x&&&xx&&x...$&&+&&$:.....:&&&;.........." +".....x&&&&&&&&&&&X..;x:........................:x&&&&x:...............................;x:..........." +"...................................................................................................." +}; + +const uint8_t OriginalMaskBits[] = { +"##################..........................##############.........................................." +"##################..........................##############.........................................." +"##############..............................#########..............................................." +"##############..............................#########..............................................." +"##################..........................#########..............................................." +"##################...........................########..............................................." +"##################...........................########..............................................." +"##################...........................##########............................................." +"##################...........................##########............................................." +}; + +const uint8_t OriginalMaskResultBits[] = { +"...................................................................................................." +"......;&&&&&&&&&&;............................;&&&&&&&&&&+.........................................." +".......xxxx+.................................+&&&;.................................................." +"........+&&&&:..............................+$&&&:.................................................." +".........:$&&&x.............................:$&&&:.................................................." +"...........+&&&&:............................x&&&x.................................................." +".......x$;..:$&&&+............................+&&&$:................................................" +".....x&&&&&&&&&&&X.............................:x&&&&x:............................................." +"...................................................................................................." +}; + +const uint8_t OriginalInvertedMaskResultBits[] = { +"...................................................................................................." +"...................................................................................................." +"..............:x:xXXXXXXX:..;XXx....;XXXXXx:..........::;XXXXXx:....:xXX;..:XXXXXXXX:XXXXXXX;;+:...." +"...............:&&&xxXxxxx:;+X&&$:+xxxxxx&&&.........:+xxXxxx&&&+..;+x&&&:+xxXxxxx;:;+xxx+&&&:......" +"...................;&&&+..x&&;x&&;.x&&$.:&&$...........x&&$.:&&$:.;&&+;&&$.:&&&xxx...:&&&x.........." +"...................;&&&+.;&&&&$&&&:x&&$x&&X............x&&$x&&X...$&&&&$&&x:&&&x:....:&&&x.........." +"...................:&&&::$&&...;&&$x&&$.x&&&x..........x&&$.x&&&xx&&x...$&&+&&$:.....:&&&;.........." +"....................;x:...............................................................;x:..........." +"...................................................................................................." +}; + +const uint32_t ImageWidth = 100; +const uint32_t ImageHeight = 9; +const uint32_t ImageBytes = ImageWidth * ImageHeight; + +static std::vector MakeTestCopy(const uint8_t* data) { + return std::vector(data, data + ImageBytes); +} + +class TransTest { +public: + std::vector ImageBits = MakeTestCopy(OriginalImageBits); + std::vector MaskBits = MakeTestCopy(OriginalMaskBits); + std::vector MaskResultBits = MakeTestCopy(OriginalMaskResultBits); + std::vector InvertedMaskResultBits = MakeTestCopy(OriginalInvertedMaskResultBits); + + ~TransTest() { + StormDestroy(); + } +}; + +TEST_CASE("STransBlt", "[transparency]") { + TransTest t; + HSTRANS trans; + + SECTION("reproduces trans mask with no colorkey") { + STransCreateI(t.MaskBits.data(), ImageWidth, ImageHeight, 8, nullptr, 0, &trans); + + std::vector result = t.ImageBits; + CHECK(STransBlt(result.data(), 0, 0, ImageWidth, trans) == 1); + CHECK(result == t.MaskBits); + } + + SECTION("produces cutout with colorkey") { + STransCreateI(t.MaskBits.data(), ImageWidth, ImageHeight, 8, nullptr, STRANS_COLORKEY('#'), &trans); + + std::vector result = t.ImageBits; + CHECK(STransBlt(result.data(), 0, 0, ImageWidth, trans) == 1); + CHECK(result == t.MaskResultBits); + } + + SECTION("produces trans mask bytes only") { + STransCreateI(t.MaskBits.data(), ImageWidth, ImageHeight, 8, nullptr, STRANS_COLORKEY('.'), &trans); + + std::vector result(ImageBytes); + CHECK(STransBlt(result.data(), 0, 0, ImageWidth, trans) == 1); + + std::vector expected = t.MaskBits; + std::replace(expected.begin(), expected.end(), static_cast('.'), static_cast(0)); + CHECK(result == expected); + } + + SECTION("produces cutout from small trans at offset") { + const uint8_t rawmask[] = { + "....#####....." + "... don't ...." + "..## let ##..." + ".### your ##.." + ".## memes ###." + "##### be #####" + ".#### dreams #" + }; + std::vector mask(std::begin(rawmask), std::end(rawmask) - 1); + + STransCreateI(mask.data(), 14, 7, 8, nullptr, 0, &trans); + + std::vector result = t.ImageBits; + CHECK(STransBlt(result.data(), 44, 1, ImageWidth, trans) == 1); + + const uint8_t rawexpected[] = { + "...................................................................................................." + "......;&&&&&&&&&&;..............................#####..............................................." + ".......xxxx+..:x:xXXXXXXX:..;XXx....;XXXXXx:... don't ....XXXXx:....:xXX;..:XXXXXXXX:XXXXXXX;;+:...." + "........+&&&&:.:&&&xxXxxxx:;+X&&$:+xxxxxx&&&..## let ##...xxx&&&+..;+x&&&:+xxXxxxx;:;+xxx+&&&:......" + ".........:$&&&x....;&&&+..x&&;x&&;.x&&$.:&&$.### your ##..$.:&&$:.;&&+;&&$.:&&&xxx...:&&&x.........." + "...........+&&&&:..;&&&+.;&&&&$&&&:x&&$x&&X..## memes ###.$x&&X...$&&&&$&&x:&&&x:....:&&&x.........." + ".......x$;..:$&&&+.:&&&::$&&...;&&$x&&$.x&&&##### be #####$.x&&&xx&&x...$&&+&&$:.....:&&&;.........." + ".....x&&&&&&&&&&&X..;x:......................#### dreams #............................;x:..........." + "...................................................................................................." + }; + std::vector expected(std::begin(rawexpected), std::end(rawexpected) - 1); + CHECK(result == expected); + } + + SECTION("produces cutout from trans rect to target") { + RECT rct = { 45, 1, 96, 7 }; + STransCreateI(t.ImageBits.data(), ImageWidth, ImageHeight, 8, &rct, STRANS_COLORKEY('.'), &trans); + + std::vector result = t.ImageBits; + CHECK(STransBlt(result.data(), 0, 1, ImageWidth, trans) == 1); + + const uint8_t rawexpected[] = { + "...................................................................................................." + ".;&&&&&&&&&&+&&&&;............................;&&&&&&&&&&+.........................................." + "+&&&;..xx::;XXXXXx:XXXX:xXX;;X:XXXXXXXX:XXXXXXX;;+:...::;XXXXXx:....:xXX;..:XXXXXXXX:XXXXXXX;;+:...." + "$&&&:...:+xxXxxx&&&+xX;+x&&&:+xxXxxxx;:;+xxx+&&&::...:+xxXxxx&&&+..;+x&&&:+xxXxxxx;:;+xxx+&&&:......" + "$&&&:....:x&&$x:&&$:&;&&+;&&$;:&&&xxx&$.:&&&x$&&&:.....x&&$.:&&$:.;&&+;&&$.:&&&xxx...:&&&x.........." + "x&&&x.....x&&$x&&X.;&$&&&&$&&x:&&&x:&&$x:&&&xx&&&x.....x&&$x&&X...$&&&&$&&x:&&&x:....:&&&x.........." + ".+&&&$:x$;x&&$&x&&&xx&&x:$&$&&+&&$:x&&$.:&&&;.+&&&$:...x&&$.x&&&xx&&x...$&&+&&$:.....:&&&;.........." + "..:x&&&&x:&&&&&&&X..;x:..................;x:...:x&&&&x:...............................;x:..........." + "...................................................................................................." + }; + std::vector expected(std::begin(rawexpected), std::end(rawexpected) - 1); + CHECK(result == expected); + } + + SECTION("does nothing for a 0x0 mask") { + RECT rct = { 7, 1, 7, 1 }; + STransCreateMaskE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, &rct, 0, &trans); + + std::vector result(ImageBytes, '.'); + CHECK(STransBlt(result.data(), 0, 0, ImageWidth, trans) == 1); + CHECK(result == std::vector(ImageBytes, '.')); + } + + SECTION("does nothing for a 1x0 mask") { + RECT rct = { 7, 1, 8, 1 }; + STransCreateMaskE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, &rct, 0, &trans); + + std::vector result(ImageBytes, '.'); + CHECK(STransBlt(result.data(), 0, 0, ImageWidth, trans) == 1); + CHECK(result == std::vector(ImageBytes, '.')); + } + + SECTION("does nothing for a 0x1 mask") { + RECT rct = { 7, 1, 7, 2 }; + STransCreateMaskE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, &rct, 0, &trans); + + std::vector result(ImageBytes, '.'); + CHECK(STransBlt(result.data(), 0, 0, ImageWidth, trans) == 1); + CHECK(result == std::vector(ImageBytes, '.')); + } +} + +TEST_CASE("STransBltUsingMask", "[transparency]") { + TransTest t; + HSTRANS trans; + + SECTION("applies inverted mask to output") { + STransCreateE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, nullptr, STRANS_COLORKEY('#'), &trans); + + std::vector dest(ImageBytes, '.'); + CHECK(STransBltUsingMask(dest.data(), t.ImageBits.data(), ImageWidth, ImageWidth, trans) == 1); + CHECK(dest == t.InvertedMaskResultBits); + } + + SECTION("applies mask to output") { + STransCreateMaskE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, nullptr, STRANS_COLORKEY('.'), &trans); + + std::vector dest(ImageBytes, '.'); + CHECK(STransBltUsingMask(dest.data(), t.ImageBits.data(), ImageWidth, ImageWidth, trans) == 1); + CHECK(dest == t.MaskResultBits); + } + + SECTION("reproduces image with no colorkey") { + STransCreateI(t.MaskBits.data(), ImageWidth, ImageHeight, 8, nullptr, 0, &trans); + + std::vector dest(ImageBytes, '.'); + CHECK(STransBltUsingMask(dest.data(), t.ImageBits.data(), ImageWidth, ImageWidth, trans) == 1); + CHECK(dest == t.ImageBits); + } + + SECTION("reproduces image with no mask colorkey") { + STransCreateMaskI(t.MaskBits.data(), ImageWidth, ImageHeight, 8, nullptr, 0, &trans); + + std::vector dest(ImageBytes, '.'); + CHECK(STransBltUsingMask(dest.data(), t.ImageBits.data(), ImageWidth, ImageWidth, trans) == 1); + CHECK(dest == t.ImageBits); + } + + SECTION("produces trans mask bytes only") { + STransCreateI(t.MaskBits.data(), ImageWidth, ImageHeight, 8, nullptr, STRANS_COLORKEY('.'), &trans); + + std::vector result(ImageBytes); + CHECK(STransBltUsingMask(result.data(), t.MaskBits.data(), ImageWidth, ImageWidth, trans) == 1); + + std::vector expected = t.MaskBits; + std::replace(expected.begin(), expected.end(), static_cast('.'), static_cast(0)); + CHECK(result == expected); + } + + SECTION("produces cutout from small trans") { + uint8_t rawmask[] = { + "....#####....." + "... don't ...." + "..## let ##..." + ".### your ##.." + ".## memes ###." + "##### be #####" + ".#### dreams #" + }; + + STransCreateMaskI(rawmask, 14, 7, 8, nullptr, STRANS_COLORKEY('.'), &trans); + + std::vector result = t.ImageBits; + CHECK(STransBltUsingMask(result.data(), rawmask, ImageWidth, 14, trans) == 1); + + const uint8_t rawexpected[] = { + "....#####..........................................................................................." + "... don't &&&&&&&;............................;&&&&&&&&&&+.........................................." + "..## let ##+..:x:xXXXXXXX:..;XXx....;XXXXXx:.+&&&;....::;XXXXXx:....:xXX;..:XXXXXXXX:XXXXXXX;;+:...." + ".### your ##&:.:&&&xxXxxxx:;+X&&$:+xxxxxx&&&+$&&&:...:+xxXxxx&&&+..;+x&&&:+xxXxxxx;:;+xxx+&&&:......" + ".## memes ###&x....;&&&+..x&&;x&&;.x&&$.:&&$:$&&&:.....x&&$.:&&$:.;&&+;&&$.:&&&xxx...:&&&x.........." + "##### be #####&&:..;&&&+.;&&&&$&&&:x&&$x&&X..x&&&x.....x&&$x&&X...$&&&&$&&x:&&&x:....:&&&x.........." + ".#### dreams #&&&+.:&&&::$&&...;&&$x&&$.x&&&x.+&&&$:...x&&$.x&&&xx&&x...$&&+&&$:.....:&&&;.........." + ".....x&&&&&&&&&&&X..;x:........................:x&&&&x:...............................;x:..........." + "...................................................................................................." + }; + std::vector expected(std::begin(rawexpected), std::end(rawexpected) - 1); + CHECK(result == expected); + } + + SECTION("produces cutout from trans rect to target") { + RECT rct = { 45, 1, 96, 7 }; + STransCreateMaskI(t.ImageBits.data(), ImageWidth, ImageHeight, 8, &rct, STRANS_COLORKEY('.'), &trans); + + std::vector result(ImageBytes, '.'); + CHECK(STransBltUsingMask(result.data(), t.ImageBits.data(), ImageWidth, ImageWidth, trans) == 1); + + const uint8_t rawexpected[] = { + "...................................................................................................." + ".........&&&&&&&&;............................;&&&&................................................." + "........xxx+..:x:xXX..XXX:..;XXx....;XXXXXx:.+&&&..................................................." + "..........&&&:.:&&&x.Xxxxx:;+.&&$:+xx...x&&&+......................................................." + "..........$&&&x......&&+..x&&;x&&;.x....:&&$:......................................................." + "...........+&&.&:..;&&&+...&&&$&&&:.....&&X........................................................." + ".......x$;...............................&&&........................................................" + "...................................................................................................." + "...................................................................................................." + }; + std::vector expected(std::begin(rawexpected), std::end(rawexpected) - 1); + CHECK(result == expected); + } + + SECTION("does nothing for a 0x0 mask") { + RECT rct = { 7, 1, 7, 1 }; + STransCreateMaskE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, &rct, 0, &trans); + + std::vector result(ImageBytes, '.'); + CHECK(STransBltUsingMask(result.data(), t.ImageBits.data(), ImageWidth, ImageWidth, trans) == 1); + CHECK(result == std::vector(ImageBytes, '.')); + } + + SECTION("does nothing for a 1x0 mask") { + RECT rct = { 7, 1, 8, 1 }; + STransCreateMaskE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, &rct, 0, &trans); + + std::vector result(ImageBytes, '.'); + CHECK(STransBltUsingMask(result.data(), t.ImageBits.data(), ImageWidth, ImageWidth, trans) == 1); + CHECK(result == std::vector(ImageBytes, '.')); + } + + SECTION("does nothing for a 0x1 mask") { + RECT rct = { 7, 1, 7, 2 }; + STransCreateMaskE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, &rct, 0, &trans); + + std::vector result(ImageBytes, '.'); + CHECK(STransBltUsingMask(result.data(), t.ImageBits.data(), ImageWidth, ImageWidth, trans) == 1); + CHECK(result == std::vector(ImageBytes, '.')); + } +} + +TEST_CASE("STransCombineMasks", "[transparency]") { + TransTest t; + HSTRANS trans, trans2; + HSTRANS combined = nullptr; + + SECTION("combines masks") { + STransCreateE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, nullptr, STRANS_COLORKEY('.'), &trans); + STransCreateE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, nullptr, STRANS_COLORKEY('#'), &trans2); + + const std::vector empty(ImageBytes, '.'); + + std::vector result = empty; + STransBltUsingMask(result.data(), t.ImageBits.data(), ImageWidth, ImageWidth, trans); + CHECK(result == t.MaskResultBits); + + result = empty; + STransBltUsingMask(result.data(), t.ImageBits.data(), ImageWidth, ImageWidth, trans2); + CHECK(result == t.InvertedMaskResultBits); + + CHECK(STransCombineMasks(trans, trans2, 0, 0, 0, &combined) == 1); + + result = empty; + STransBltUsingMask(result.data(), t.ImageBits.data(), ImageWidth, ImageWidth, combined); + CHECK(result == t.ImageBits); + } + + SECTION("combines masks inverted") { + STransCreateMaskE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, nullptr, STRANS_COLORKEY('.'), &trans); + STransCreateMaskE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, nullptr, STRANS_COLORKEY('#'), &trans2); + + const std::vector empty(ImageBytes, '.'); + + std::vector result = empty; + STransBltUsingMask(result.data(), t.ImageBits.data(), ImageWidth, ImageWidth, trans); + CHECK(result == t.MaskResultBits); + + result = empty; + STransBltUsingMask(result.data(), t.ImageBits.data(), ImageWidth, ImageWidth, trans2); + CHECK(result == t.InvertedMaskResultBits); + + CHECK(STransCombineMasks(trans, trans2, 0, 0, 0, &combined) == 1); + + result = empty; + STransBltUsingMask(result.data(), t.ImageBits.data(), ImageWidth, ImageWidth, combined); + CHECK(result == t.ImageBits); + } + + SECTION("combines with self") { + uint32_t flag = GENERATE(0, STRANS_CF_INTERSECT); + STransCreateMaskE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, nullptr, STRANS_COLORKEY('.'), &trans); + + CHECK(STransCombineMasks(trans, trans, 0, 0, flag, &combined) == 1); + + std::vector result(ImageBytes, '.'); + STransBltUsingMask(result.data(), t.ImageBits.data(), ImageWidth, ImageWidth, combined); + CHECK(result == t.MaskResultBits); + } + + SECTION("combines with inverted self") { + STransCreateMaskE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, nullptr, STRANS_COLORKEY('.'), &trans); + + CHECK(STransCombineMasks(trans, trans, 0, 0, STRANS_CF_INVERTSECOND, &combined) == 1); + + std::vector result(ImageBytes, '.'); + STransBltUsingMask(result.data(), t.ImageBits.data(), ImageWidth, ImageWidth, combined); + CHECK(result == t.ImageBits); + } + + SECTION("subtracts from self") { + STransCreateMaskE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, nullptr, STRANS_COLORKEY('.'), &trans); + + CHECK(STransCombineMasks(trans, trans, 0, 0, STRANS_CF_SUBTRACT, &combined) == 1); + + std::vector result(ImageBytes, '.'); + STransBltUsingMask(result.data(), t.ImageBits.data(), ImageWidth, ImageWidth, combined); + CHECK(result == std::vector(ImageBytes, '.')); + } + + SECTION("combines with empty trans") { + STransCreateMaskE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, nullptr, STRANS_COLORKEY('.'), &trans); + STransCreateMaskE(t.MaskBits.data(), 1, 1, 8, nullptr, 0, &trans2); + + CHECK(STransCombineMasks(trans, trans2, 0, 0, 0, &combined) == 1); + + std::vector result(ImageBytes, '.'); + STransBltUsingMask(result.data(), t.ImageBits.data(), ImageWidth, ImageWidth, combined); + CHECK(result == t.MaskResultBits); + } + + SECTION("combines with gap and offset") { + uint8_t rawSMask[] = { + "##################.................................................................................." + "##################.................................................................................." + "##############......................................................................................" + "##############......................................................................................" + "##################.................................................................................." + "##################.................................................................................." + "##################.................................................................................." + "##################.................................................................................." + "##################.................................................................................." + }; + uint8_t rawCMask[] = { + "##############" + "#########....." + "#########....." + "#########....." + ".########....." + ".########....." + ".##########..." + }; + STransCreateMaskE(rawSMask, ImageWidth, ImageHeight, 8, nullptr, STRANS_COLORKEY('.'), &trans); + STransCreateMaskE(rawCMask, 14, 7, 8, nullptr, STRANS_COLORKEY('.'), &trans2); + + CHECK(STransCombineMasks(trans, trans2, 44, 1, 0, &combined) == 1); + + std::vector result(ImageBytes, '.'); + STransBltUsingMask(result.data(), t.ImageBits.data(), ImageWidth, ImageWidth, combined); + CHECK(result == t.MaskResultBits); + } + + SECTION("excludes gap combination out of bounds") { + uint8_t rawSMask[] = { + "##################" + "##################" + "##############...." + "##############...." + "##################" + "##################" + "##################" + "##################" + "##################" + }; + uint8_t rawCMask[] = { + "##############" + "#########....." + "#########....." + "#########....." + ".########....." + ".########....." + ".##########..." + }; + STransCreateMaskE(rawSMask, 18, ImageHeight, 8, nullptr, STRANS_COLORKEY('.'), &trans); + STransCreateMaskE(rawCMask, 14, 7, 8, nullptr, STRANS_COLORKEY('.'), &trans2); + + CHECK(STransCombineMasks(trans, trans2, 44, 1, 0, &combined) == 1); + + std::vector result(ImageBytes, '.'); + STransBltUsingMask(result.data(), t.ImageBits.data(), ImageWidth, ImageWidth, combined); + + const uint8_t expectedraw[] = { + "...................................................................................................." + "......;&&&&&&&&&&;.................................................................................." + ".......xxxx+........................................................................................" + "........+&&&&:......................................................................................" + ".........:$&&&x....................................................................................." + "...........+&&&&:..................................................................................." + ".......x$;..:$&&&+.................................................................................." + ".....x&&&&&&&&&&&X.................................................................................." + "...................................................................................................." + }; + std::vector expected(std::begin(expectedraw), std::end(expectedraw) - 1); + CHECK(result == expected); + } + + SECTION("combines with self using invert flag") { + STransCreateMaskE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, nullptr, STRANS_COLORKEY('.'), &trans); + + const std::vector empty(ImageBytes, '.'); + + std::vector result = empty; + STransBltUsingMask(result.data(), t.ImageBits.data(), ImageWidth, ImageWidth, trans); + CHECK(result == t.MaskResultBits); + + CHECK(STransCombineMasks(trans, trans, 0, 0, STRANS_CF_INVERTSECOND, &combined) == 1); + + result = empty; + STransBltUsingMask(result.data(), t.ImageBits.data(), ImageWidth, ImageWidth, combined); + CHECK(result == t.ImageBits); + } + + SECTION("combines using intersect flag") { + STransCreateMaskE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, nullptr, STRANS_COLORKEY('.'), &trans); + STransCreateMaskE(t.ImageBits.data(), ImageWidth, ImageHeight, 8, nullptr, STRANS_COLORKEY('.'), &trans2); + + CHECK(STransCombineMasks(trans, trans2, 0, 0, STRANS_CF_INTERSECT, &combined) == 1); + + std::vector result(ImageBytes, '.'); + STransBltUsingMask(result.data(), t.MaskBits.data(), ImageWidth, ImageWidth, combined); + + const uint8_t expectedraw[] = { + "...................................................................................................." + "......############............................############.........................................." + ".......#####.................................#####.................................................." + "........######..............................######.................................................." + ".........######.............................######.................................................." + "...........######............................#####.................................................." + ".......###..######............................######................................................" + ".....#############.............................########............................................." + "...................................................................................................." + }; + std::vector expected(std::begin(expectedraw), std::end(expectedraw) - 1); + CHECK(result == expected); + } + + SECTION("combines with subtract") { + STransCreateMaskE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, nullptr, STRANS_COLORKEY('.'), &trans); + STransCreateMaskE(t.ImageBits.data(), ImageWidth, ImageHeight, 8, nullptr, STRANS_COLORKEY('.'), &trans2); + + CHECK(STransCombineMasks(trans, trans2, 0, 0, STRANS_CF_SUBTRACT, &combined) == 1); + + std::vector result(ImageBytes, '.'); + STransBltUsingMask(result.data(), t.MaskBits.data(), ImageWidth, ImageWidth, combined); + + const uint8_t expectedraw[] = { + "##################..........................##############.........................................." + "######......................................##......................................................" + "#######.....##..............................#.....###..............................................." + "########..........................................###..............................................." + "#########......###................................###..............................................." + "###########......#................................###..............................................." + "#######...##.................................#......#..............................................." + "#####........................................##....................................................." + "##################...........................##########............................................." + }; + std::vector expected(std::begin(expectedraw), std::end(expectedraw) - 1); + CHECK(result == expected); + } + + SECTION("combines with 0x0 mask") { + STransCreateMaskE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, nullptr, STRANS_COLORKEY('.'), &trans); + + RECT rct = { 7, 1, 7, 1 }; + STransCreateMaskE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, &rct, 0, &trans2); + + CHECK(STransCombineMasks(trans, trans2, 0, 0, STRANS_CF_INTERSECT, &combined) == 1); + + std::vector result(ImageBytes, '.'); + STransBltUsingMask(result.data(), t.MaskBits.data(), ImageWidth, ImageWidth, combined); + CHECK(result == std::vector(ImageBytes, '.')); + } + + SECTION("combines with 1x0 mask") { + STransCreateMaskE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, nullptr, STRANS_COLORKEY('.'), &trans); + + RECT rct = { 7, 1, 8, 1 }; + STransCreateMaskE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, &rct, 0, &trans2); + + CHECK(STransCombineMasks(trans, trans2, 0, 0, STRANS_CF_INTERSECT, &combined) == 1); + + std::vector result(ImageBytes, '.'); + STransBltUsingMask(result.data(), t.MaskBits.data(), ImageWidth, ImageWidth, combined); + CHECK(result == std::vector(ImageBytes, '.')); + } + + SECTION("combines with 0x1 mask") { + STransCreateMaskE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, nullptr, STRANS_COLORKEY('.'), &trans); + + RECT rct = { 7, 1, 7, 2 }; + STransCreateMaskE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, &rct, 0, &trans2); + + CHECK(STransCombineMasks(trans, trans2, 0, 0, STRANS_CF_INTERSECT, &combined) == 1); + + std::vector result(ImageBytes, '.'); + STransBltUsingMask(result.data(), t.MaskBits.data(), ImageWidth, ImageWidth, combined); + CHECK(result == std::vector(ImageBytes, '.')); + } + + // TODO test after STransDelete for s_savedata (investigate) +} + +TEST_CASE("STransCreateE", "[transparency]") { + TransTest t; + + SECTION("fails if bitdepth is not 8") { + int32_t bitdepth = GENERATE(-1, 0, 1, 2, 4, 7, 9, 16, 32); + HSTRANS handle = reinterpret_cast(12345); + CHECK_FALSE(STransCreateE(t.ImageBits.data(), ImageWidth, ImageHeight, bitdepth, nullptr, 0, &handle)); + CHECK(handle == nullptr); + } + + SECTION("creates a handle") { + HSTRANS handle = nullptr; + CHECK(STransCreateE(t.ImageBits.data(), ImageWidth, ImageHeight, 8, nullptr, 0, &handle) == 1); + CHECK(handle != nullptr); + } + + SECTION("creates a 1x1 handle") { + HSTRANS handle = nullptr; + CHECK(STransCreateE(t.ImageBits.data(), 1, 1, 8, nullptr, 0, &handle) == 1); + REQUIRE(handle != nullptr); + + std::vector result(4); + STransBlt(result.data(), 0, 0, 2, handle); + + std::vector expected = { '.', 0, 0, 0 }; + CHECK(result == expected); + } + + SECTION("creates a handle using a rect") { + HSTRANS handle = nullptr; + RECT rct = { 10, 2, 90, 7 }; + CHECK(STransCreateE(t.ImageBits.data(), ImageWidth, ImageHeight, 8, &rct, 0, &handle) == 1); + CHECK(handle != nullptr); + } + + SECTION("creates a 1x1 handle using a rect") { + HSTRANS handle = nullptr; + RECT rct = { 7, 1, 8, 2 }; + CHECK(STransCreateE(t.ImageBits.data(), ImageWidth, ImageHeight, 8, &rct, 0, &handle) == 1); + REQUIRE(handle != nullptr); + + std::vector result(4); + STransBlt(result.data(), 0, 0, 2, handle); + + std::vector expected = { '&', 0, 0, 0 }; + CHECK(result == expected); + } + + SECTION("creates a 0x0 handle using a rect") { + HSTRANS handle = nullptr; + RECT rct = { 1, 1, 1, 1 }; + CHECK(STransCreateE(t.ImageBits.data(), ImageWidth, ImageHeight, 8, &rct, 0, &handle) == 1); + REQUIRE(handle != nullptr); + + std::vector result(4); + STransBlt(result.data(), 0, 0, 2, handle); + + std::vector expected = { 0, 0, 0, 0 }; + CHECK(result == expected); + } + + SECTION("creates a trans mask opposite to colorkey") { + HSTRANS trans; + CHECK(STransCreateE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, nullptr, STRANS_COLORKEY('#'), &trans) == 1); + + std::vector result(ImageBytes, '.'); + STransBltUsingMask(result.data(), t.ImageBits.data(), ImageWidth, ImageWidth, trans); + CHECK(result == t.InvertedMaskResultBits); + } +} + +TEST_CASE("STransCreateI", "[transparency]") { + TransTest t; + + SECTION("fails if bitdepth is not 8") { + int32_t bitdepth = GENERATE(-1, 0, 1, 2, 4, 7, 9, 16, 32); + HSTRANS handle = reinterpret_cast(12345); + CHECK_FALSE(STransCreateI(t.ImageBits.data(), ImageWidth, ImageHeight, bitdepth, nullptr, 0, &handle)); + CHECK(handle == nullptr); + } + + SECTION("creates a handle") { + HSTRANS handle = nullptr; + CHECK(STransCreateI(t.ImageBits.data(), ImageWidth, ImageHeight, 8, nullptr, 0, &handle) == 1); + CHECK(handle != nullptr); + } + + SECTION("creates a 1x1 handle") { + HSTRANS handle = nullptr; + CHECK(STransCreateI(t.ImageBits.data(), 1, 1, 8, nullptr, 0, &handle) == 1); + CHECK(handle != nullptr); + } + + SECTION("creates a handle using a rect") { + HSTRANS handle = nullptr; + RECT rct = { 10, 2, 90, 7 }; + CHECK(STransCreateI(t.ImageBits.data(), ImageWidth, ImageHeight, 8, &rct, 0, &handle) == 1); + CHECK(handle != nullptr); + } + + SECTION("creates a 1x1 handle using a rect") { + HSTRANS handle = nullptr; + RECT rct = { 7, 1, 7, 1 }; + CHECK(STransCreateI(t.ImageBits.data(), ImageWidth, ImageHeight, 8, &rct, 0, &handle) == 1); + REQUIRE(handle != nullptr); + + std::vector result(4); + STransBlt(result.data(), 0, 0, 2, handle); + + std::vector expected = { '&', 0, 0, 0 }; + CHECK(result == expected); + } + + SECTION("creates a 0x0 handle using a rect") { + HSTRANS handle = nullptr; + RECT rct = { 1, 1, 0, 0 }; + CHECK(STransCreateI(t.ImageBits.data(), ImageWidth, ImageHeight, 8, &rct, 0, &handle) == 1); + REQUIRE(handle != nullptr); + + std::vector result(4); + STransBlt(result.data(), 0, 0, 2, handle); + + std::vector expected = { 0, 0, 0, 0 }; + CHECK(result == expected); + } + + SECTION("creates a trans mask opposite to colorkey") { + HSTRANS trans; + CHECK(STransCreateI(t.MaskBits.data(), ImageWidth, ImageHeight, 8, nullptr, STRANS_COLORKEY('#'), &trans) == 1); + + std::vector result(ImageBytes, '.'); + STransBltUsingMask(result.data(), t.ImageBits.data(), ImageWidth, ImageWidth, trans); + CHECK(result == t.InvertedMaskResultBits); + } +} + +TEST_CASE("STransCreateMaskE", "[transparency]") { + TransTest t; + + SECTION("fails if bitdepth is not 8") { + int32_t bitdepth = GENERATE(-1, 0, 1, 2, 4, 7, 9, 16, 32); + HSTRANS handle = reinterpret_cast(12345); + CHECK_FALSE(STransCreateMaskE(t.ImageBits.data(), ImageWidth, ImageHeight, bitdepth, nullptr, 0, &handle)); + CHECK(handle == nullptr); + } + + SECTION("creates a handle") { + HSTRANS handle = nullptr; + CHECK(STransCreateMaskE(t.ImageBits.data(), ImageWidth, ImageHeight, 8, nullptr, 0, &handle) == 1); + CHECK(handle != nullptr); + } + + SECTION("creates a 1x1 handle") { + HSTRANS handle = nullptr; + CHECK(STransCreateMaskE(t.ImageBits.data(), 1, 1, 8, nullptr, 0, &handle) == 1); + CHECK(handle != nullptr); + } + + SECTION("creates a handle using a rect") { + HSTRANS handle = nullptr; + RECT rct = { 10, 2, 90, 7 }; + CHECK(STransCreateMaskE(t.ImageBits.data(), ImageWidth, ImageHeight, 8, &rct, 0, &handle) == 1); + CHECK(handle != nullptr); + } + + SECTION("creates a 1x1 handle using a rect") { + HSTRANS handle = nullptr; + RECT rct = { 7, 1, 8, 2 }; + CHECK(STransCreateMaskE(t.ImageBits.data(), ImageWidth, ImageHeight, 8, &rct, STRANS_COLORKEY('.'), &handle) == 1); + REQUIRE(handle != nullptr); + + std::vector result(4); + STransBlt(result.data(), 0, 0, 2, handle); + + std::vector expected = { 1, 0, 0, 0 }; + CHECK(result == expected); + } + + SECTION("creates a 0x0 handle using a rect") { + HSTRANS handle = nullptr; + RECT rct = { 1, 1, 1, 1 }; + CHECK(STransCreateMaskE(t.ImageBits.data(), ImageWidth, ImageHeight, 8, &rct, STRANS_COLORKEY('.'), &handle) == 1); + REQUIRE(handle != nullptr); + + std::vector result(4); + STransBlt(result.data(), 0, 0, 2, handle); + + std::vector expected = { 0, 0, 0, 0 }; + CHECK(result == expected); + } + + SECTION("creates a trans mask that matches colorkey") { + HSTRANS trans; + CHECK(STransCreateMaskE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, nullptr, STRANS_COLORKEY('#'), &trans) == 1); + + std::vector result(ImageBytes, '.'); + STransBltUsingMask(result.data(), t.ImageBits.data(), ImageWidth, ImageWidth, trans); + CHECK(result == t.InvertedMaskResultBits); + } +} + +TEST_CASE("STransCreateMaskI", "[transparency]") { + TransTest t; + + SECTION("fails if bitdepth is not 8") { + int32_t bitdepth = GENERATE(-1, 0, 1, 2, 4, 7, 9, 16, 32); + HSTRANS handle = reinterpret_cast(12345); + CHECK_FALSE(STransCreateMaskI(t.ImageBits.data(), ImageWidth, ImageHeight, bitdepth, nullptr, 0, &handle)); + CHECK(handle == nullptr); + } + + SECTION("creates a handle") { + HSTRANS handle = nullptr; + CHECK(STransCreateMaskI(t.ImageBits.data(), ImageWidth, ImageHeight, 8, nullptr, 0, &handle) == 1); + CHECK(handle != nullptr); + } + + SECTION("creates a 1x1 handle") { + HSTRANS handle = nullptr; + CHECK(STransCreateMaskI(t.ImageBits.data(), 1, 1, 8, nullptr, 0, &handle) == 1); + CHECK(handle != nullptr); + } + + SECTION("creates a handle using a rect") { + HSTRANS handle = nullptr; + RECT rct = { 10, 2, 90, 7 }; + CHECK(STransCreateMaskI(t.ImageBits.data(), ImageWidth, ImageHeight, 8, &rct, 0, &handle) == 1); + CHECK(handle != nullptr); + } + + SECTION("creates a 1x1 handle using a rect") { + HSTRANS handle = nullptr; + RECT rct = { 7, 1, 7, 1 }; + CHECK(STransCreateMaskI(t.ImageBits.data(), ImageWidth, ImageHeight, 8, &rct, STRANS_COLORKEY('.'), &handle) == 1); + REQUIRE(handle != nullptr); + + std::vector result(4); + STransBlt(result.data(), 0, 0, 2, handle); + + std::vector expected = { 1, 0, 0, 0 }; + CHECK(result == expected); + } + + SECTION("creates a 0x0 handle using a rect") { + HSTRANS handle = nullptr; + RECT rct = { 1, 1, 0, 0 }; + CHECK(STransCreateMaskI(t.ImageBits.data(), ImageWidth, ImageHeight, 8, &rct, STRANS_COLORKEY('.'), &handle) == 1); + REQUIRE(handle != nullptr); + + std::vector result(4); + STransBlt(result.data(), 0, 0, 2, handle); + + std::vector expected = { 0, 0, 0, 0 }; + CHECK(result == expected); + } + + SECTION("creates a trans mask that matches colorkey") { + HSTRANS trans; + CHECK(STransCreateMaskI(t.MaskBits.data(), ImageWidth, ImageHeight, 8, nullptr, STRANS_COLORKEY('#'), &trans) == 1); + + std::vector result(ImageBytes, '.'); + STransBltUsingMask(result.data(), t.ImageBits.data(), ImageWidth, ImageWidth, trans); + CHECK(result == t.InvertedMaskResultBits); + } +} + +TEST_CASE("STransDelete", "[transparency]") { + TransTest t; + + SECTION("called successfully") { + HSTRANS handle = nullptr; + CHECK(STransCreateE(t.ImageBits.data(), ImageWidth, ImageHeight, 8, nullptr, 0, &handle)); + CHECK(STransDelete(handle) == 1); + } +} + +#if !defined(WHOA_TEST_STORMDLL) +TEST_CASE("STransDestroy", "[transparency]") { + SECTION("called successfully") { + CHECK(STransDestroy() == 1); + } +} +#endif + +TEST_CASE("STransDuplicate", "[transparency]") { + TransTest t; + HSTRANS trans, copy; + + SECTION("duplicates a transparency record") { + STransCreateE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, nullptr, STRANS_COLORKEY('#'), &trans); + + CHECK(STransDuplicate(trans, ©) == 1); + STransDelete(trans); + + std::vector result = t.ImageBits; + CHECK(STransBlt(result.data(), 0, 0, ImageWidth, copy) == 1); + CHECK(result == t.MaskResultBits); + } + + SECTION("duplicates a 0x0 mask") { + RECT rct = { 7, 1, 7, 1 }; + STransCreateMaskE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, &rct, STRANS_COLORKEY('.'), &trans); + + CHECK(STransDuplicate(trans, ©) == 1); + STransDelete(trans); + + std::vector result(ImageBytes, '.'); + CHECK(STransBltUsingMask(result.data(), t.ImageBits.data(), ImageWidth, ImageWidth, copy) == 1); + CHECK(result == std::vector(ImageBytes, '.')); + } + + SECTION("duplicates a 1x0 mask") { + RECT rct = { 7, 1, 8, 1 }; + STransCreateMaskE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, &rct, STRANS_COLORKEY('.'), &trans); + + CHECK(STransDuplicate(trans, ©) == 1); + STransDelete(trans); + + std::vector result(ImageBytes, '.'); + CHECK(STransBltUsingMask(result.data(), t.ImageBits.data(), ImageWidth, ImageWidth, copy) == 1); + CHECK(result == std::vector(ImageBytes, '.')); + } + + SECTION("duplicates a 0x1 mask") { + RECT rct = { 7, 1, 7, 2 }; + STransCreateMaskE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, &rct, STRANS_COLORKEY('.'), &trans); + + CHECK(STransDuplicate(trans, ©) == 1); + STransDelete(trans); + + std::vector result(ImageBytes, '.'); + CHECK(STransBltUsingMask(result.data(), t.ImageBits.data(), ImageWidth, ImageWidth, copy) == 1); + CHECK(result == std::vector(ImageBytes, '.')); + } +} + +TEST_CASE("STransIntersectDirtyArray", "[transparency]") { + TransTest t; + HSTRANS mask; + HSTRANS trans; + + SECTION("does nothing if dirty info hasn't been set") { + STransCreateE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, nullptr, 0, &mask); + + trans = reinterpret_cast(1234); + CHECK_FALSE(STransIntersectDirtyArray(mask, t.ImageBits.data(), 1, &trans)); + CHECK(trans == nullptr); + } + + SECTION("intersects dirty array with source mask") { + STransSetDirtyArrayInfo(ImageWidth, ImageHeight, 4, 1); + + uint8_t dirty[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, + 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + + STransCreateMaskI(t.MaskBits.data(), ImageWidth, ImageHeight, 8, nullptr, STRANS_COLORKEY('.'), &mask); + CHECK(STransIntersectDirtyArray(mask, dirty, 1, &trans) == 1); + + std::vector dest(ImageBytes, '.'); + CHECK(STransBltUsingMask(dest.data(), t.ImageBits.data(), ImageWidth, ImageWidth, trans)); + + // This is a strict subset of the S C array (MaskResultBits) + const uint8_t rawexpected[] = { + "...................................................................................................." + "......;&&&&&&&&&&;............................;&&&&&&&&&............................................" + "........xxx+.................................+&&&;.................................................." + "........+&&&................................+$&&&:.................................................." + ".........:$&&&x.............................:$&&&:.................................................." + "............&&&&.............................x&&&x.................................................." + "............:$&&................................&&$:................................................" + ".....x&&&&&&&&&&................................x&&&&x:............................................." + "...................................................................................................." + }; + std::vector expected(std::begin(rawexpected), std::end(rawexpected) - 1); + CHECK(dest == expected); + } + + SECTION("applies mask with dirty array scale") { + STransSetDirtyArrayInfo(ImageWidth, ImageHeight, 8, 2); + + uint8_t dirty[] = { + 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, + 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, + 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, + 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + }; + + STransCreateI(t.MaskBits.data(), ImageWidth, ImageHeight, 8, nullptr, STRANS_COLORKEY('#'), &mask); + CHECK(STransIntersectDirtyArray(mask, dirty, 1, &trans) == 1); + + std::vector dest(ImageBytes, '.'); + CHECK(STransBltUsingMask(dest.data(), t.ImageBits.data(), ImageWidth, ImageWidth, trans)); + + const uint8_t rawexpected[] = { + "...................................................................................................." + "...................................................................................................." + "................:xXXXXXXX:..;XXx....;XXXXXx:..........::............:xXX;..:XXXXXXXX:XXX............" + "................&&&xxXxxxx:;+X&&$:+xxxxxx&&&.........:+x........+..;+x&&&:+xxXxxxx;:;+xx............" + "..........................x&&;x&.......................x&&$.:&&$........&$.:&&&xxx...:&&&x.........." + ".........................;&&&&$&.......................x&&$x&&X.........&&x:&&&x:....:&&&x.........." + "...................:&&&::$&&...;&&$x&&$................x........xx&&x...$&&+&&$:...................." + "....................;x:............................................................................." + "...................................................................................................." + }; + std::vector expected(std::begin(rawexpected), std::end(rawexpected) - 1); + CHECK(dest == expected); + } + + SECTION("only applies mask which passes dirtyarraymask test") { + STransSetDirtyArrayInfo(ImageWidth, ImageHeight, 8, 2); + + uint8_t dirty[] = { + 0, 0, 0, 2, 2, 2, 1, 0, 0, 1, 1, 0, 0, + 0, 0, 1, 3, 3, 3, 2, 0, 1, 1, 1, 0, 0, + 0, 0, 2, 3, 7, 7, 1, 1, 0, 255, 1, 1, 0, + 0, 0, 1, 6, 7, 7, 2, 0, 1, 1, 0, 0, 0, + 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + }; + + STransCreateI(t.MaskBits.data(), ImageWidth, ImageHeight, 8, nullptr, STRANS_COLORKEY('#'), &mask); + CHECK(STransIntersectDirtyArray(mask, dirty, 2, &trans) == 1); + + std::vector dest(ImageBytes, '.'); + CHECK(STransBltUsingMask(dest.data(), t.ImageBits.data(), ImageWidth, ImageWidth, trans)); + + const uint8_t rawexpected[] = { + "...................................................................................................." + "...................................................................................................." + "........................X:..;XXx....;XXXXXx:..........::............................................" + "........................xx:;+X&&$:+xxxxxx&&&.........:+x............................................" + "...................;&&&+..x&&;x&&;.x&&$.:&&$............................&$.:&&&x...................." + "...................;&&&+.;&&&&$&&&:x&&$x&&X.............................&&x:&&&x...................." + "........................:$&&...;&&$x&&$.x&&&x..........x............................................" + "...................................................................................................." + "...................................................................................................." + }; + std::vector expected(std::begin(rawexpected), std::end(rawexpected) - 1); + CHECK(dest == expected); + } + + SECTION("applies whole mask if 1x1 dirty array is set") { + STransSetDirtyArrayInfo(ImageWidth, ImageHeight, 128, 16); + + uint8_t dirty[] = { 1 }; + STransCreateMaskE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, nullptr, STRANS_COLORKEY('#'), &mask); + + CHECK(STransIntersectDirtyArray(mask, dirty, 1, &trans) == 1); + + std::vector dest(ImageBytes, '.'); + CHECK(STransBltUsingMask(dest.data(), t.ImageBits.data(), ImageWidth, ImageWidth, trans)); + CHECK(dest == t.InvertedMaskResultBits); + } + + SECTION("applies nothing if 1x1 dirty array is unset") { + STransSetDirtyArrayInfo(ImageWidth, ImageHeight, 128, 16); + + uint8_t dirty[] = { 0 }; + STransCreateMaskE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, nullptr, STRANS_COLORKEY('#'), &mask); + + CHECK(STransIntersectDirtyArray(mask, dirty, 1, &trans) == 1); + + std::vector dest(ImageBytes, '.'); + CHECK(STransBltUsingMask(dest.data(), t.ImageBits.data(), ImageWidth, ImageWidth, trans)); + CHECK(dest == std::vector(ImageBytes, '.')); + } + + SECTION("does nothing on a 0x0 mask") { + STransSetDirtyArrayInfo(ImageWidth, ImageHeight, 16, 4); + + std::vector dirty((ImageWidth / 16 + 1) * (ImageHeight / 4 + 1), 1); + + RECT rct = { 7, 1, 7, 1 }; + STransCreateMaskE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, &rct, STRANS_COLORKEY('#'), &mask); + CHECK(STransIntersectDirtyArray(mask, dirty.data(), 1, &trans) == 1); + + std::vector dest(ImageBytes, '.'); + CHECK(STransBltUsingMask(dest.data(), t.ImageBits.data(), ImageWidth, ImageWidth, trans)); + CHECK(dest == std::vector(ImageBytes, '.')); + } + + SECTION("does nothing on a 0x1 mask") { + STransSetDirtyArrayInfo(ImageWidth, ImageHeight, 16, 4); + + std::vector dirty((ImageWidth / 16 + 1) * (ImageHeight / 4 + 1), 1); + + RECT rct = { 7, 1, 7, 2 }; + STransCreateMaskE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, &rct, STRANS_COLORKEY('#'), &mask); + CHECK(STransIntersectDirtyArray(mask, dirty.data(), 1, &trans) == 1); + + std::vector dest(ImageBytes, '.'); + CHECK(STransBltUsingMask(dest.data(), t.ImageBits.data(), ImageWidth, ImageWidth, trans)); + CHECK(dest == std::vector(ImageBytes, '.')); + } + + SECTION("does nothing on a 1x0 mask") { + STransSetDirtyArrayInfo(ImageWidth, ImageHeight, 16, 4); + + std::vector dirty((ImageWidth / 16 + 1) * (ImageHeight / 4 + 1), 1); + + RECT rct = { 7, 1, 8, 1 }; + STransCreateMaskE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, &rct, STRANS_COLORKEY('#'), &mask); + CHECK(STransIntersectDirtyArray(mask, dirty.data(), 1, &trans) == 1); + + std::vector dest(ImageBytes, '.'); + CHECK(STransBltUsingMask(dest.data(), t.ImageBits.data(), ImageWidth, ImageWidth, trans)); + CHECK(dest == std::vector(ImageBytes, '.')); + } + + // TODO test after STransDelete for s_savedata (investigate) +} + +TEST_CASE("STransInvertMask", "[transparency]") { + TransTest t; + HSTRANS trans; + HSTRANS inverted = nullptr; + + SECTION("creates a new transparency record") { + STransCreateE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, nullptr, 0, &trans); + + REQUIRE(STransInvertMask(trans, &inverted) == 1); + STransDelete(trans); + + REQUIRE(inverted != nullptr); + + std::vector dest(ImageBytes, '.'); + STransBltUsingMask(dest.data(), t.ImageBits.data(), ImageWidth, ImageWidth, inverted); + CHECK(dest == std::vector(ImageBytes, '.')); + } + + SECTION("inverts a mask") { + STransCreateE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, nullptr, STRANS_COLORKEY('.'), &trans); + + std::vector dest(ImageBytes, '.'); + std::vector source = t.ImageBits; + CHECK(STransBltUsingMask(dest.data(), source.data(), ImageWidth, ImageWidth, trans)); + CHECK(dest == t.MaskResultBits); + + REQUIRE(STransInvertMask(trans, &inverted) == 1); + + std::vector dest2(ImageBytes, '.'); + CHECK(STransBltUsingMask(dest2.data(), source.data(), ImageWidth, ImageWidth, inverted)); + CHECK(dest2 == t.InvertedMaskResultBits); + } + + SECTION("does nothing for a 0x0 mask") { + RECT rct = { 7, 1, 7, 1 }; + STransCreateMaskE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, &rct, STRANS_COLORKEY('.'), &trans); + + REQUIRE(STransInvertMask(trans, &inverted) == 1); + + std::vector dest(ImageBytes, '.'); + STransBltUsingMask(dest.data(), t.ImageBits.data(), ImageWidth, ImageWidth, inverted); + CHECK(dest == std::vector(ImageBytes, '.')); + } + + SECTION("does nothing for a 1x0 mask") { + RECT rct = { 7, 1, 7, 2 }; + STransCreateMaskE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, &rct, STRANS_COLORKEY('.'), &trans); + + REQUIRE(STransInvertMask(trans, &inverted) == 1); + + std::vector dest(ImageBytes, '.'); + STransBltUsingMask(dest.data(), t.ImageBits.data(), ImageWidth, ImageWidth, inverted); + CHECK(dest == std::vector(ImageBytes, '.')); + } + + SECTION("does nothing for a 0x1 mask") { + RECT rct = { 7, 1, 8, 1 }; + STransCreateMaskE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, &rct, STRANS_COLORKEY('.'), &trans); + + REQUIRE(STransInvertMask(trans, &inverted) == 1); + + std::vector dest(ImageBytes, '.'); + STransBltUsingMask(dest.data(), t.ImageBits.data(), ImageWidth, ImageWidth, inverted); + CHECK(dest == std::vector(ImageBytes, '.')); + } +} + +TEST_CASE("STransIsPixelInMask", "[transparency]") { + TransTest t; + HSTRANS trans; + + SECTION("false if coordinates are invalid") { + auto pos = GENERATE( + std::make_pair(-1, 0), + std::make_pair(0, -1), + std::make_pair(1, 0), + std::make_pair(0, 1), + std::make_pair(1, 1), + std::make_pair(-1, -1) + ); + STransCreateE(t.ImageBits.data(), 1, 1, 8, nullptr, 0, &trans); + CHECK_FALSE(STransIsPixelInMask(trans, pos.first, pos.second)); + } + + SECTION("true if coordinates are valid") { + auto pos = GENERATE( + std::make_pair(0, 0), + std::make_pair(0, 4), + std::make_pair(4, 0), + std::make_pair(4, 4) + ); + STransCreateE(t.ImageBits.data(), 5, 5, 8, nullptr, 0, &trans); + CHECK(STransIsPixelInMask(trans, pos.first, pos.second) == 1); + } + + SECTION("false if coordinates are invalid on rect and colorkey") { + auto pos = GENERATE( + std::make_pair(0, 4), + std::make_pair(44, 3), + std::make_pair(24, 0), + std::make_pair(37, 0), + std::make_pair(32, 4), + std::make_pair(25, 4) + ); + RECT rct = { 20, 1, 64, 5 }; + STransCreateE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, &rct, STRANS_COLORKEY('#'), &trans); + + std::ostringstream ss("Pos: "); + ss << pos.first << ", " << pos.second; + INFO(ss.str()); + + CHECK_FALSE(STransIsPixelInMask(trans, pos.first, pos.second)); + } + + SECTION("true if coordinates are valid on rect and colorkey") { + auto pos = GENERATE( + std::make_pair(23, 0), + std::make_pair(38, 0), + std::make_pair(33, 4), + std::make_pair(24, 4) + ); + RECT rct = { 20, 1, 64, 6 }; + STransCreateE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, &rct, STRANS_COLORKEY('#'), &trans); + + std::ostringstream ss("Pos: "); + ss << pos.first << ", " << pos.second; + INFO(ss.str()); + + CHECK(STransIsPixelInMask(trans, pos.first, pos.second) == 1); + } + + SECTION("true if coordinates are valid on rect and colorkey (inverted)") { + auto pos = GENERATE( + std::make_pair(24, 0), + std::make_pair(37, 0), + std::make_pair(32, 4), + std::make_pair(25, 4) + ); + RECT rct = { 20, 1, 64, 6 }; + // Inverted mask using colour key here + STransCreateE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, &rct, STRANS_COLORKEY('.'), &trans); + + std::ostringstream ss("Pos: "); + ss << pos.first << ", " << pos.second; + INFO(ss.str()); + + CHECK(STransIsPixelInMask(trans, pos.first, pos.second) == 1); + } + + SECTION("false for a 0x0 mask") { + RECT rct = { 7, 1, 7, 1 }; + STransCreateMaskE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, &rct, 0, &trans); + CHECK_FALSE(STransIsPixelInMask(trans, 0, 0)); + CHECK_FALSE(STransIsPixelInMask(trans, 7, 1)); + } + + SECTION("false for a 1x0 mask") { + RECT rct = { 7, 1, 8, 1 }; + STransCreateMaskE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, &rct, 0, &trans); + CHECK_FALSE(STransIsPixelInMask(trans, 0, 0)); + CHECK_FALSE(STransIsPixelInMask(trans, 7, 1)); + } + + SECTION("false for a 0x1 mask") { + RECT rct = { 7, 1, 7, 2 }; + STransCreateMaskE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, &rct, 0, &trans); + CHECK_FALSE(STransIsPixelInMask(trans, 0, 0)); + CHECK_FALSE(STransIsPixelInMask(trans, 7, 1)); + } +} + +TEST_CASE("STransSetDirtyArrayInfo", "[transparency]") { + TransTest t; + HSTRANS mask, trans; + + SECTION("clears dirty offset and fails if cx is <= 0") { + int32_t cx = GENERATE(0, -1, INT_MIN); + CHECK_FALSE(STransSetDirtyArrayInfo(0, 0, cx, 42)); + + STransCreateE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, nullptr, 0, &mask); + CHECK_FALSE(STransIntersectDirtyArray(mask, t.ImageBits.data(), 1, &trans)); + } + + SECTION("clears dirty offset and fails if cy is <= 0") { + int32_t cy = GENERATE(0, -1, INT_MIN); + CHECK_FALSE(STransSetDirtyArrayInfo(0, 0, 42, cy)); + + STransCreateE(t.MaskBits.data(), ImageWidth, ImageHeight, 8, nullptr, 0, &mask); + CHECK_FALSE(STransIntersectDirtyArray(mask, t.ImageBits.data(), 1, &trans)); + } + + SECTION("fails if cx is not power of 2") { + int32_t cx = GENERATE(3, 5, 6, 9, 255); + CHECK_FALSE(STransSetDirtyArrayInfo(8, 8, cx, 1)); + } + + SECTION("fails if cy is not power of 2") { + int32_t cy = GENERATE(3, 5, 6, 9, 255); + CHECK_FALSE(STransSetDirtyArrayInfo(8, 8, 1, cy)); + } + + SECTION("allocates dirty info") { + CHECK(STransSetDirtyArrayInfo(8, 8, 1, 1) == 1); + + std::vector dirty(8 * 8, 0); + STransCreateE(t.MaskBits.data(), 4, 4, 8, nullptr, 0, &mask); + CHECK(STransUpdateDirtyArray(dirty.data(), 1, 0, 0, mask) == 1); + + std::vector expected = { + 1, 1, 1, 1, 0, 0, 0, 0, + 1, 1, 1, 1, 0, 0, 0, 0, + 1, 1, 1, 1, 0, 0, 0, 0, + 1, 1, 1, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }; + CHECK(dirty == expected); + } + + SECTION("sets x axis scale") { + CHECK(STransSetDirtyArrayInfo(8, 8, 4, 1) == 1); + + std::vector dirty(2 * 8, 0); + STransCreateE(t.MaskBits.data(), 4, 4, 8, nullptr, 0, &mask); + CHECK(STransUpdateDirtyArray(dirty.data(), 1, 0, 0, mask) == 1); + + std::vector expected = { + 1, 0, + 1, 0, + 1, 0, + 1, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + }; + CHECK(dirty == expected); + } + + SECTION("sets y axis scale") { + CHECK(STransSetDirtyArrayInfo(8, 8, 1, 4) == 1); + + std::vector dirty(8 * 2, 0); + STransCreateE(t.MaskBits.data(), 4, 4, 8, nullptr, 0, &mask); + CHECK(STransUpdateDirtyArray(dirty.data(), 1, 0, 0, mask) == 1); + + std::vector expected = { + 1, 1, 1, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }; + CHECK(dirty == expected); + } +} + +TEST_CASE("STransUpdateDirtyArray", "[transparency]") { + TransTest t; + HSTRANS trans, transtest; + + SECTION("fails if dirty offset is not set") { + STransCreateE(t.ImageBits.data(), ImageWidth, ImageHeight, 8, nullptr, 0, &trans); + CHECK_FALSE(STransUpdateDirtyArray(t.MaskBits.data(), 1, 0, 0, trans)); + } + + SECTION("fails if trans width <= 0") { + STransSetDirtyArrayInfo(ImageWidth, ImageHeight, 1, 1); + + std::vector dirty(ImageBytes, 0); + // confirm that dirty info is initialized + STransCreateE(t.ImageBits.data(), ImageWidth, ImageHeight, 8, nullptr, 0, &transtest); + CHECK(STransUpdateDirtyArray(dirty.data(), 1, 0, 0, transtest) == 1); + + RECT rct = { 0, 0, 0, 5 }; + STransCreateE(t.ImageBits.data(), ImageWidth, ImageHeight, 8, &rct, 0, &trans); + CHECK_FALSE(STransUpdateDirtyArray(dirty.data(), 1, 0, 0, trans)); + } + + SECTION("fails if trans height <= 0") { + STransSetDirtyArrayInfo(ImageWidth, ImageHeight, 1, 1); + + std::vector dirty(ImageBytes, 0); + // confirm that dirty info is initialized + STransCreateE(t.ImageBits.data(), ImageWidth, ImageHeight, 8, nullptr, 0, &transtest); + CHECK(STransUpdateDirtyArray(dirty.data(), 1, 0, 0, transtest) == 1); + + RECT rct = { 0, 0, 5, 0 }; + STransCreateE(t.ImageBits.data(), ImageWidth, ImageHeight, 8, &rct, 0, &trans); + CHECK_FALSE(STransUpdateDirtyArray(dirty.data(), 1, 0, 0, trans)); + } + + SECTION("fails if tracecontour is true") { + STransSetDirtyArrayInfo(ImageWidth, ImageHeight, 1, 1); + + std::vector dirty(ImageBytes, 0); + // confirm that dirty info is initialized + STransCreateE(t.ImageBits.data(), ImageWidth, ImageHeight, 8, nullptr, 0, &transtest); + CHECK(STransUpdateDirtyArray(dirty.data(), 1, 0, 0, transtest) == 1); + + int32_t tracecontour = GENERATE(-1, 1, INT_MIN, INT_MAX); + STransCreateE(t.ImageBits.data(), ImageWidth, ImageHeight, 8, nullptr, 0, &trans); + CHECK_FALSE(STransUpdateDirtyArray(dirty.data(), 1, 0, 0, trans, tracecontour)); + } + + SECTION("updates only trans area with x scale") { + STransSetDirtyArrayInfo(8, 8, 4, 1); + + std::vector dirty(2 * 8, 0); + STransCreateE(t.MaskBits.data(), 4, 4, 8, nullptr, 0, &trans); + CHECK(STransUpdateDirtyArray(dirty.data(), 1, 0, 0, trans) == 1); + + std::vector expected = { + 1, 0, + 1, 0, + 1, 0, + 1, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + }; + CHECK(dirty == expected); + } + + SECTION("updates only trans area with y scale") { + STransSetDirtyArrayInfo(8, 8, 1, 4); + + std::vector dirty(8 * 2, 0); + STransCreateE(t.MaskBits.data(), 4, 4, 8, nullptr, 0, &trans); + CHECK(STransUpdateDirtyArray(dirty.data(), 1, 0, 0, trans) == 1); + + std::vector expected = { + 1, 1, 1, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }; + CHECK(dirty == expected); + } + + SECTION("updates area specified by trans rect and position") { + STransSetDirtyArrayInfo(8, 8, 1, 1); + + std::vector dirty(8 * 8, 0); + RECT rct = { 3, 5, 7, 7 }; + STransCreateI(t.MaskBits.data(), 8, 8, 8, &rct, 0, &trans); + CHECK(STransUpdateDirtyArray(dirty.data(), 1, 3, 2, trans) == 1); + + std::vector expected = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 1, 1, 1, 1, + 0, 0, 0, 1, 1, 1, 1, 1, + 0, 0, 0, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }; + CHECK(dirty == expected); + } + + SECTION("applies OR operation on dirty array") { + STransSetDirtyArrayInfo(3, 3, 1, 1); + + std::vector dirty(3 * 3, 0); + STransCreateI(t.MaskBits.data(), 3, 3, 8, nullptr, 0, &trans); + STransCreateI(t.MaskBits.data(), 1, 2, 8, nullptr, 0, &transtest); + + CHECK(STransUpdateDirtyArray(dirty.data(), 1, 0, 0, trans) == 1); + + std::vector expected = { + 1, 1, 1, + 1, 1, 1, + 1, 1, 1, + }; + CHECK(dirty == expected); + + CHECK(STransUpdateDirtyArray(dirty.data(), 6, 1, 0, transtest) == 1); + expected = { + 1, 7, 1, + 1, 7, 1, + 1, 1, 1, + }; + CHECK(dirty == expected); + + CHECK(STransUpdateDirtyArray(dirty.data(), 8, 0, 0, trans) == 1); + expected = { + 9, 15, 9, + 9, 15, 9, + 9, 9, 9, + }; + CHECK(dirty == expected); + + CHECK(STransUpdateDirtyArray(dirty.data(), 0xFF, 0, 0, trans) == 1); + expected = { + 255, 255, 255, + 255, 255, 255, + 255, 255, 255, + }; + CHECK(dirty == expected); + } + + SECTION("chooses appropriate positions with scaling") { + STransSetDirtyArrayInfo(7 * 16, 5 * 4, 16, 4); + + std::vector dirty(7 * 5, 0); + STransCreateI(t.MaskBits.data(), 40, 6, 8, nullptr, 0, &trans); + + CHECK(STransUpdateDirtyArray(dirty.data(), 1, 56, 3, trans) == 1); + + std::vector expected = { + 0, 0, 0, 1, 1, 1, 0, + 0, 0, 0, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + }; + CHECK(dirty == expected); + + CHECK(STransUpdateDirtyArray(dirty.data(), 2, 40, 2, trans) == 1); + + expected = { + 0, 0, 2, 3, 3, 1, 0, + 0, 0, 2, 3, 3, 1, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + }; + CHECK(dirty == expected); + + CHECK(STransUpdateDirtyArray(dirty.data(), 4, 50, 1, trans) == 1); + + expected = { + 0, 0, 2, 7, 7, 1, 0, + 0, 0, 2, 3, 3, 1, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + }; + CHECK(dirty == expected); + + CHECK(STransUpdateDirtyArray(dirty.data(), 8, 63, 8, trans) == 1); + + expected = { + 0, 0, 2, 7, 7, 1, 0, + 0, 0, 2, 3, 3, 1, 0, + 0, 0, 0, 8, 8, 8, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + }; + CHECK(dirty == expected); + } +} diff --git a/test/stormdll/storm.def b/test/stormdll/storm.def index 1567551..ff0d3a4 100644 --- a/test/stormdll/storm.def +++ b/test/stormdll/storm.def @@ -245,22 +245,22 @@ EXPORTS ;SRegGetNumSubKeys @430 NONAME ; Transparency - ;STransBlt @431 NONAME - ;STransBltUsingMask @432 NONAME - ;STransCreateI @433 NONAME - ;STransDelete @434 NONAME + STransBlt @431 NONAME + STransBltUsingMask @432 NONAME + STransCreateI @433 NONAME + STransDelete @434 NONAME ;STransDestroy @435 NONAME - ;STransDuplicate @436 NONAME - ;STransIntersectDirtyArray @437 NONAME - ;STransInvertMask @438 NONAME + STransDuplicate @436 NONAME + STransIntersectDirtyArray @437 NONAME + STransInvertMask @438 NONAME ;STransLoadI @439 NONAME - ;STransSetDirtyArrayInfo @440 NONAME - ;STransUpdateDirtyArray @441 NONAME - ;STransIsPixelInMask @442 NONAME - ;STransCombineMasks @443 NONAME - ;STransCreateMaskI @444 NONAME - ;STransCreateE @445 NONAME - ;STransCreateMaskE @446 NONAME + STransSetDirtyArrayInfo @440 NONAME + STransUpdateDirtyArray @441 NONAME + STransIsPixelInMask @442 NONAME + STransCombineMasks @443 NONAME + STransCreateMaskI @444 NONAME + STransCreateE @445 NONAME + STransCreateMaskE @446 NONAME ;STransLoadE @447 NONAME ; Video diff --git a/test/stormdll/stormstubs.cpp b/test/stormdll/stormstubs.cpp index d7fda4c..9bee1a2 100644 --- a/test/stormdll/stormstubs.cpp +++ b/test/stormdll/stormstubs.cpp @@ -117,6 +117,24 @@ int32_t STORMAPI SStrToInt(const char*) { return 0; } uint32_t STORMAPI SStrToUnsigned(const char*) { return 0; } void STORMAPI SStrUpper(char*) {} +#include + +int32_t STORMAPI STransBlt(uint8_t*, int32_t, int32_t, int32_t, HSTRANS) { return 0; } +int32_t STORMAPI STransBltUsingMask(uint8_t*, uint8_t*, int32_t, int32_t, HSTRANS) { return 0; } +int32_t STORMAPI STransCombineMasks(HSTRANS, HSTRANS, int32_t, int32_t, uint32_t, HSTRANS*) { return 0; } +int32_t STORMAPI STransCreateE(uint8_t*, int32_t, int32_t, int32_t, RECT*, uint32_t, HSTRANS*) { return 0; } +int32_t STORMAPI STransCreateI(uint8_t*, int32_t, int32_t, int32_t, RECT*, uint32_t, HSTRANS*) { return 0; } +int32_t STORMAPI STransCreateMaskE(uint8_t*, int32_t, int32_t, int32_t, RECT*, uint32_t, HSTRANS*) { return 0; } +int32_t STORMAPI STransCreateMaskI(uint8_t*, int32_t, int32_t, int32_t, RECT*, uint32_t, HSTRANS*) { return 0; } +int32_t STORMAPI STransDelete(HSTRANS) { return 0; } +int32_t STORMAPI STransDestroy() { return 0; } +int32_t STORMAPI STransDuplicate(HSTRANS, HSTRANS*) { return 0; } +int32_t STORMAPI STransIntersectDirtyArray(HSTRANS, uint8_t*, uint8_t, HSTRANS*) { return 0; } +int32_t STORMAPI STransInvertMask(HSTRANS, HSTRANS*) { return 0; } +int32_t STORMAPI STransIsPixelInMask(HSTRANS, int32_t, int32_t) { return 0; } +int32_t STORMAPI STransSetDirtyArrayInfo(int32_t, int32_t, int32_t, int32_t) { return 0; } +int32_t STORMAPI STransUpdateDirtyArray(uint8_t*, uint8_t, int32_t, int32_t, HSTRANS, int32_t) { return 0; } + #include ptrdiff_t STORMAPI SUniConvertUTF16ToDos(char*, const char16_t*, uint32_t) { return 0; }; diff --git a/test/trans/TransInternal.cpp b/test/trans/TransInternal.cpp new file mode 100644 index 0000000..39843aa --- /dev/null +++ b/test/trans/TransInternal.cpp @@ -0,0 +1,120 @@ +#include "test/Test.hpp" +#include "storm/Transparency.hpp" +#include "storm/List.hpp" + +#include +#include + +extern STORM_LIST(TRANS) s_translist; + +struct TRANS : TSLinkedNode { + uint8_t* data; + uint32_t dataalloc; + uint32_t databytes; + uint32_t instructionoffset; + int32_t width; + int32_t height; + RECT boundrect; +}; + +const uint32_t ImageWidth = 100; +const uint32_t ImageHeight = 9; + +uint8_t MaskBits[] = { +"##################..........................##############.........................................." +"##################..........................##############.........................................." +"##############..............................#########..............................................." +"##############..............................#########..............................................." +"##################..........................#########..............................................." +"##################...........................########..............................................." +"##################...........................########..............................................." +"##################...........................##########............................................." +"##################...........................##########............................................." +}; + +// Retrieved from Starcraft 1.17's Storm.dll output +const std::vector ExpectedInternalBytes = { + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x00, + 0x00, 0x12, 0x1A, 0x0E, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x12, 0x1A, 0x0E, 0x2A, 0x00, 0x00, 0x00, + 0x00, 0x0E, 0x1E, 0x09, 0x2F, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x1E, 0x09, 0x2F, 0x00, 0x00, 0x00, + 0x00, 0x12, 0x1A, 0x09, 0x2F, 0x00, 0x00, 0x00, 0x00, 0x12, 0x1B, 0x08, 0x2F, 0x00, 0x00, 0x00, + 0x00, 0x12, 0x1B, 0x08, 0x2F, 0x00, 0x00, 0x00, 0x00, 0x12, 0x1B, 0x0A, 0x2D, 0x00, 0x00, 0x00, + 0x00, 0x12, 0x1B, 0x0A, 0x2D, 0x00, 0x00, 0x00, +}; + +TEST_CASE("STransCreateE internal", "[transparency]") { + SECTION("produces correct bytes") { + HSTRANS trans; + REQUIRE(STransCreateE(MaskBits, ImageWidth, ImageHeight, 8, nullptr, STRANS_COLORKEY('#'), &trans) == 1); + + auto result = std::vector(trans->data, trans->data + trans->databytes); + + // This one specific byte actually gets skipped + // The allocation mechanism differs in squall so it ends up being unintentionally different, but has no meaning + result[655] = 0; + + CHECK(result == ExpectedInternalBytes); + + STransDestroy(); // cleanup + } +} + +bool TransListContains(HSTRANS trans) { + for (TRANS* pTrans = s_translist.Head(); pTrans; pTrans = pTrans->Next()) { + if (pTrans == trans) return true; + } + return false; +} + +TEST_CASE("STransDelete internal", "[transparency]") { + SECTION("removes the transparency record") { + HSTRANS trans; + CHECK(STransCreateE(MaskBits, ImageWidth, ImageHeight, 8, nullptr, 0, &trans)); + + CHECK(TransListContains(trans)); + CHECK(STransDelete(trans) == 1); + CHECK_FALSE(TransListContains(trans)); + + STransDestroy(); // cleanup + } +}