diff options
author | Kae <80987908+Novaenia@users.noreply.github.com> | 2023-06-20 14:33:09 +1000 |
---|---|---|
committer | Kae <80987908+Novaenia@users.noreply.github.com> | 2023-06-20 14:33:09 +1000 |
commit | 6352e8e3196f78388b6c771073f9e03eaa612673 (patch) | |
tree | e23772f79a7fbc41bc9108951e9e136857484bf4 /source/game/StarImageMetadataDatabase.cpp | |
parent | 6741a057e5639280d85d0f88ba26f000baa58f61 (diff) |
everything everywhere
all at once
Diffstat (limited to 'source/game/StarImageMetadataDatabase.cpp')
-rw-r--r-- | source/game/StarImageMetadataDatabase.cpp | 251 |
1 files changed, 251 insertions, 0 deletions
diff --git a/source/game/StarImageMetadataDatabase.cpp b/source/game/StarImageMetadataDatabase.cpp new file mode 100644 index 0000000..613ed1a --- /dev/null +++ b/source/game/StarImageMetadataDatabase.cpp @@ -0,0 +1,251 @@ +#include "StarImageMetadataDatabase.hpp" +#include "StarFile.hpp" +#include "StarImage.hpp" +#include "StarImageProcessing.hpp" +#include "StarLogging.hpp" +#include "StarEncode.hpp" +#include "StarGameTypes.hpp" +#include "StarRoot.hpp" +#include "StarAssets.hpp" + +namespace Star { + +Vec2U ImageMetadataDatabase::imageSize(String const& path) const { + MutexLocker locker(m_mutex); + auto i = m_sizeCache.find(path); + if (i != m_sizeCache.end()) + return i->second; + + locker.unlock(); + Vec2U size = calculateImageSize(path); + + locker.lock(); + m_sizeCache[path] = size; + return size; +} + +List<Vec2I> ImageMetadataDatabase::imageSpaces(String const& path, Vec2F position, float fillLimit, bool flip) const { + SpacesEntry key = make_tuple(path, Vec2I::round(position), fillLimit, flip); + + MutexLocker locker(m_mutex); + auto i = m_spacesCache.find(key); + if (i != m_spacesCache.end()) { + return i->second; + } + + String filteredPath = filterProcessing(path); + SpacesEntry filteredKey = make_tuple(filteredPath, Vec2I::round(position), fillLimit, flip); + + auto j = m_spacesCache.find(filteredKey); + if (j != m_spacesCache.end()) { + auto spaces = j->second; + m_spacesCache[key] = spaces; + return spaces; + } + + locker.unlock(); + + auto image = Root::singleton().assets()->image(filteredPath); + int imageWidth = image->width(); + int imageHeight = image->height(); + + Vec2I min((position / TilePixels).floor()); + Vec2I max(((Vec2F(imageWidth, imageHeight) + position) / TilePixels).ceil()); + + List<Vec2I> spaces; + + for (int yspace = min[1]; yspace < max[1]; ++yspace) { + for (int xspace = min[0]; xspace < max[0]; ++xspace) { + float fillRatio = 0.0f; + + for (int y = 0; y < (int)TilePixels; ++y) { + int ypixel = round(yspace * (int)TilePixels + y - position[1]); + if (ypixel < 0 || ypixel >= imageHeight) + continue; + + for (int x = 0; x < (int)TilePixels; ++x) { + int xpixel = round(xspace * (int)TilePixels + x - position[0]); + if (flip) + xpixel = imageWidth - 1 - xpixel; + + if (xpixel < 0 || xpixel >= imageWidth) + continue; + + if (image->get(xpixel, ypixel)[3] > 0) + fillRatio += 1.0f / square(TilePixels); + } + } + + if (fillRatio >= fillLimit) + spaces.append(Vec2I(xspace, yspace)); + } + } + + locker.lock(); + m_spacesCache[key] = spaces; + m_spacesCache[filteredKey] = spaces; + + return spaces; +} + +RectU ImageMetadataDatabase::nonEmptyRegion(String const& path) const { + MutexLocker locker(m_mutex); + auto i = m_regionCache.find(path); + if (i != m_regionCache.end()) { + return i->second; + } + + String filteredPath = filterProcessing(path); + auto j = m_regionCache.find(filteredPath); + if (j != m_regionCache.end()) { + m_regionCache[path] = j->second; + return j->second; + } + + locker.unlock(); + auto image = Root::singleton().assets()->image(filteredPath); + RectU region = RectU::null(); + image->forEachPixel([®ion](unsigned x, unsigned y, Vec4B const& pixel) { + if (pixel[3] > 0) + region.combine(RectU::withSize({x, y}, {1, 1})); + }); + + locker.lock(); + m_regionCache[path] = region; + m_regionCache[filteredPath] = region; + + return region; +} + +String ImageMetadataDatabase::filterProcessing(String const& path) { + AssetPath components = AssetPath::split(path); + + components.directives.filter([](String const& directive) { + ImageOperation operation; + try { + operation = imageOperationFromString(directive); + } catch (StarException const&) { + return true; + } + + return !(operation.is<HueShiftImageOperation>() || + operation.is<SaturationShiftImageOperation>() || + operation.is<BrightnessMultiplyImageOperation>() || + operation.is<FadeToColorImageOperation>() || + operation.is<ScanLinesImageOperation>() || + operation.is<SetColorImageOperation>()); + }); + + return AssetPath::join(components); +} + +Vec2U ImageMetadataDatabase::calculateImageSize(String const& path) const { + // Carefully calculate an image's size while trying not to actually load it. + // In error cases, this will fall back to calling Assets::image, so that image + // can possibly produce a missing image asset or properly report the error. + + auto assets = Root::singleton().assets(); + + auto fallback = [&assets, &path]() { + return assets->image(path)->size(); + }; + + AssetPath components = AssetPath::split(path); + if (!assets->assetExists(components.basePath)) { + return fallback(); + } + + Vec2U imageSize; + if (components.subPath) { + auto frames = assets->imageFrames(components.basePath); + if (!frames) + return fallback(); + + if (auto rect = frames->getRect(*components.subPath)) + imageSize = rect->size(); + else + return fallback(); + } else { + // We ensure that the base image size is cached even when given directives, + // so we don't have to call Image::readPngMetadata on the same file more + // than once. + MutexLocker locker(m_mutex); + if (auto size = m_sizeCache.maybe(components.basePath)) { + imageSize = *size; + } else { + locker.unlock(); + imageSize = get<0>(Image::readPngMetadata(assets->openFile(components.basePath))); + locker.lock(); + m_sizeCache[components.basePath] = imageSize; + } + } + + struct OperationSizeAdjust { + Vec2U& imageSize; + bool hasError; + + OperationSizeAdjust(Vec2U& size) : imageSize(size), hasError(false) {}; + + void operator()(HueShiftImageOperation const&) {} + + void operator()(SaturationShiftImageOperation const&) {} + + void operator()(BrightnessMultiplyImageOperation const&) {} + + void operator()(FadeToColorImageOperation const&) {} + + void operator()(ScanLinesImageOperation const&) {} + + void operator()(SetColorImageOperation const&) {} + + void operator()(ColorReplaceImageOperation const&) {} + + void operator()(AlphaMaskImageOperation const&) {} + + void operator()(BlendImageOperation const&) {} + + void operator()(MultiplyImageOperation const&) {} + + void operator()(BorderImageOperation const& bio) { + imageSize += Vec2U::filled(bio.pixels * 2); + } + + void operator()(ScaleImageOperation const& sio) { + imageSize = Vec2U::round(vmult(Vec2F(imageSize), sio.scale)); + } + + void operator()(CropImageOperation const& cio) { + if (cio.subset.isEmpty() || + cio.subset.xMin() < 0 || + cio.subset.yMin() < 0 || + (unsigned)cio.subset.xMax() > imageSize[0] || + (unsigned)cio.subset.yMax() > imageSize[1]) { + hasError = true; + } else { + imageSize = Vec2U(cio.subset.size()); + } + } + + void operator()(FlipImageOperation const&) {} + }; + + OperationSizeAdjust osa(imageSize); + + for (auto const& directive : components.directives) { + ImageOperation operation; + try { + operation = imageOperationFromString(directive); + } catch (StarException const&) { + return fallback(); + } + + operation.call(osa); + if (osa.hasError) { + return fallback(); + } + } + + return imageSize; +} + +} |