From 8cf6c5423a032aebbdc6ef49cb129524412a0e4e Mon Sep 17 00:00:00 2001 From: Eldred Habert Date: Fri, 2 May 2025 05:39:52 +0200 Subject: [PATCH] Implement `--background-color` (#1508) Co-authored-by: Rangi42 --- contrib/bash_compl/_rgbgfx.bash | 1 + contrib/zsh_compl/_rgbgfx | 1 + include/gfx/main.hpp | 26 ++++++ man/rgbgfx.1 | 15 ++- src/gfx/main.cpp | 41 ++++++++- src/gfx/pal_spec.cpp | 21 ----- src/gfx/process.cpp | 101 ++++++++++++++------- test/gfx/bg_fuse.err | 3 + test/gfx/bg_fuse.flags | 1 + test/gfx/bg_fuse.png | Bin 0 -> 177 bytes test/gfx/bg_in_tile.err | 2 + test/gfx/bg_in_tile.flags | 1 + test/gfx/bg_in_tile.png | Bin 0 -> 181 bytes test/gfx/bg_oval.flags | 1 + test/gfx/bg_oval.out.2bpp | Bin 0 -> 96 bytes test/gfx/bg_oval.out.attrmap | Bin 0 -> 9 bytes test/gfx/bg_oval.out.pal | Bin 0 -> 8 bytes test/gfx/bg_oval.out.tilemap | Bin 0 -> 9 bytes test/gfx/bg_oval.png | Bin 0 -> 334 bytes test/gfx/input_tileset_with_bg.flags | 4 + test/gfx/input_tileset_with_bg.in.2bpp | Bin 0 -> 64 bytes test/gfx/input_tileset_with_bg.in.pal | Bin 0 -> 8 bytes test/gfx/input_tileset_with_bg.out.2bpp | Bin 0 -> 64 bytes test/gfx/input_tileset_with_bg.out.attrmap | Bin 0 -> 16 bytes test/gfx/input_tileset_with_bg.out.tilemap | Bin 0 -> 16 bytes test/gfx/input_tileset_with_bg.png | Bin 0 -> 255 bytes test/gfx/trans_bg_in_tile.err | 2 + test/gfx/trans_bg_in_tile.flags | 1 + test/gfx/trans_bg_in_tile.png | Bin 0 -> 179 bytes 29 files changed, 167 insertions(+), 54 deletions(-) create mode 100644 test/gfx/bg_fuse.err create mode 100644 test/gfx/bg_fuse.flags create mode 100644 test/gfx/bg_fuse.png create mode 100644 test/gfx/bg_in_tile.err create mode 100644 test/gfx/bg_in_tile.flags create mode 100644 test/gfx/bg_in_tile.png create mode 100644 test/gfx/bg_oval.flags create mode 100644 test/gfx/bg_oval.out.2bpp create mode 100644 test/gfx/bg_oval.out.attrmap create mode 100644 test/gfx/bg_oval.out.pal create mode 100644 test/gfx/bg_oval.out.tilemap create mode 100644 test/gfx/bg_oval.png create mode 100644 test/gfx/input_tileset_with_bg.flags create mode 100644 test/gfx/input_tileset_with_bg.in.2bpp create mode 100644 test/gfx/input_tileset_with_bg.in.pal create mode 100644 test/gfx/input_tileset_with_bg.out.2bpp create mode 100644 test/gfx/input_tileset_with_bg.out.attrmap create mode 100644 test/gfx/input_tileset_with_bg.out.tilemap create mode 100644 test/gfx/input_tileset_with_bg.png create mode 100644 test/gfx/trans_bg_in_tile.err create mode 100644 test/gfx/trans_bg_in_tile.flags create mode 100644 test/gfx/trans_bg_in_tile.png diff --git a/contrib/bash_compl/_rgbgfx.bash b/contrib/bash_compl/_rgbgfx.bash index eb7bff44..9d49dd7e 100755 --- a/contrib/bash_compl/_rgbgfx.bash +++ b/contrib/bash_compl/_rgbgfx.bash @@ -19,6 +19,7 @@ _rgbgfx_completions() { [Z]="columns:normal" [a]="attr-map:glob-*.attrmap" [A]="auto-attr-map:normal" + [B]="background-color:unk" [b]="base-tiles:unk" [c]="colors:unk" [d]="depth:unk" diff --git a/contrib/zsh_compl/_rgbgfx b/contrib/zsh_compl/_rgbgfx index 49d9596a..91f474c6 100644 --- a/contrib/zsh_compl/_rgbgfx +++ b/contrib/zsh_compl/_rgbgfx @@ -28,6 +28,7 @@ local args=( '(-Z --columns)'{-Z,--columns}'[Read the image in column-major order]' '(-a --attr-map -A --auto-attr-map)'{-a,--attr-map}'+[Generate a map of tile attributes (mirroring)]:attrmap file:_files' + '(-B --background-color)'{-B,--background-color}'+[Ignore tiles containing only specified color]:color:' '(-b --base-tiles)'{-b,--base-tiles}'+[Base tile IDs for tile map output]:base tile IDs:' '(-c --colors)'{-c,--colors}'+[Specify color palettes]:palette spec:' '(-d --depth)'{-d,--depth}'+[Set bit depth]:bit depth:_depths' diff --git a/include/gfx/main.hpp b/include/gfx/main.hpp index 28435b49..f951cca6 100644 --- a/include/gfx/main.hpp +++ b/include/gfx/main.hpp @@ -10,6 +10,8 @@ #include #include +#include "helpers.hpp" + #include "gfx/rgba.hpp" struct Options { @@ -21,6 +23,7 @@ struct Options { uint8_t verbosity = 0; // -v std::string attrmap{}; // -a, -A + std::optional bgColor{}; // -B std::array baseTileIDs{0, 0}; // -b enum { NO_SPEC, @@ -116,4 +119,27 @@ static constexpr auto flipTable = ([]() constexpr { return table; })(); +// Parsing helpers. + +static constexpr uint8_t nibble(char c) { + if (c >= 'a') { + assume(c <= 'f'); + return c - 'a' + 10; + } else if (c >= 'A') { + assume(c <= 'F'); + return c - 'A' + 10; + } else { + assume(c >= '0' && c <= '9'); + return c - '0'; + } +} + +static constexpr uint8_t toHex(char c1, char c2) { + return nibble(c1) * 16 + nibble(c2); +} + +static constexpr uint8_t singleToHex(char c) { + return toHex(c, c); +} + #endif // RGBDS_GFX_MAIN_HPP diff --git a/man/rgbgfx.1 b/man/rgbgfx.1 index d9a83a45..369bd937 100644 --- a/man/rgbgfx.1 +++ b/man/rgbgfx.1 @@ -103,6 +103,19 @@ and has the same size. Same as .Fl a Ar base_path Ns .attrmap .Pq see Sx Automatic output paths . +.It Fl B Ar color , Fl \-background-color Ar color +Set a background color to be omitted from output. +Colors are accepted in +.Ql #rgb +or +.Ql #rrggbb +format, or as +.Ql transparent . +Input tiles which are entirely the specified background color are ignored and will not be output in tile data file. +The tilemap, atrribute map, or palette map files +.Em will +use placeholder values where background tiles were. +If a background color is specified, it cannot be used within tiles which are not ignored. .It Fl b Ar base_ids , Fl \-base-tiles Ar base_ids Set the base IDs for tile map output. .Ar base_ids @@ -126,7 +139,7 @@ begins with a hash character .Ql # , it is treated as an inline palette specification. It should contain a comma-separated list of hexadecimal colors, each beginning with a hash. -Colors are accepted either as +Colors are accepted in .Ql #rgb or .Ql #rrggbb diff --git a/src/gfx/main.cpp b/src/gfx/main.cpp index bd0bbc18..06a1187b 100644 --- a/src/gfx/main.cpp +++ b/src/gfx/main.cpp @@ -114,7 +114,7 @@ void Options::verbosePrint(uint8_t level, char const *fmt, ...) const { } // Short options -static char const *optstring = "-Aa:b:Cc:d:hi:L:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvXx:YZ"; +static char const *optstring = "-Aa:B:b:Cc:d:hi:L:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvXx:YZ"; // Equivalent long options // Please keep in the same order as short opts. @@ -126,6 +126,7 @@ static char const *optstring = "-Aa:b:Cc:d:hi:L:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvXx:YZ" static option const longopts[] = { {"auto-attr-map", no_argument, nullptr, 'A'}, {"attr-map", required_argument, nullptr, 'a'}, + {"background-color", required_argument, nullptr, 'B'}, {"base-tiles", required_argument, nullptr, 'b'}, {"color-curve", no_argument, nullptr, 'C'}, {"colors", required_argument, nullptr, 'c'}, @@ -353,6 +354,7 @@ static char *parseArgv(int argc, char *argv[]) { for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) { char *arg = musl_optarg; // Make a copy for scanning uint16_t number; + size_t size; switch (ch) { case 'A': localOptions.autoAttrmap = true; @@ -364,6 +366,43 @@ static char *parseArgv(int argc, char *argv[]) { } options.attrmap = musl_optarg; break; + case 'B': + if (strcasecmp(musl_optarg, "transparent") == 0) { + options.bgColor = Rgba(0x00, 0x00, 0x00, 0x00); + break; + } + if (musl_optarg[0] != '#') { + error("Background color specification must be `#rgb`, `#rrggbb`, or `transparent`"); + break; + } + size = strspn(&musl_optarg[1], "0123456789ABCDEFabcdef"); + switch (size) { + case 3: + options.bgColor = Rgba( + singleToHex(musl_optarg[1]), + singleToHex(musl_optarg[2]), + singleToHex(musl_optarg[3]), + 0xFF + ); + break; + case 6: + options.bgColor = Rgba( + toHex(musl_optarg[1], musl_optarg[2]), + toHex(musl_optarg[3], musl_optarg[4]), + toHex(musl_optarg[5], musl_optarg[6]), + 0xFF + ); + break; + default: + error("Unknown background color specification \"%s\"", musl_optarg); + } + if (musl_optarg[size + 1] != '\0') { + error( + "Unexpected text \"%s\" after background color specification", + &musl_optarg[size + 1] + ); + } + break; case 'b': number = parseNumber(arg, "Bank 0 base tile ID", 0); if (number >= 256) { diff --git a/src/gfx/pal_spec.cpp b/src/gfx/pal_spec.cpp index d87d672c..ac444f58 100644 --- a/src/gfx/pal_spec.cpp +++ b/src/gfx/pal_spec.cpp @@ -23,27 +23,6 @@ using namespace std::string_view_literals; -constexpr uint8_t nibble(char c) { - if (c >= 'a') { - assume(c <= 'f'); - return c - 'a' + 10; - } else if (c >= 'A') { - assume(c <= 'F'); - return c - 'A' + 10; - } else { - assume(c >= '0' && c <= '9'); - return c - '0'; - } -} - -constexpr uint8_t toHex(char c1, char c2) { - return nibble(c1) * 16 + nibble(c2); -} - -constexpr uint8_t singleToHex(char c) { - return toHex(c, c); -} - template // Should be std::string or std::string_view static void skipWhitespace(Str const &str, size_t &pos) { pos = std::min(str.find_first_not_of(" \t"sv, pos), str.length()); diff --git a/src/gfx/process.cpp b/src/gfx/process.cpp index f27fb3e2..8506ab9f 100644 --- a/src/gfx/process.cpp +++ b/src/gfx/process.cpp @@ -25,6 +25,10 @@ #include "gfx/pal_sorting.hpp" #include "gfx/proto_palette.hpp" +static bool isBgColorTransparent() { + return options.bgColor.has_value() && options.bgColor->isTransparent(); +} + class ImagePalette { std::array, NB_COLOR_SLOTS> _colors; @@ -38,7 +42,7 @@ public: Rgba const *registerColor(Rgba const &rgba) { std::optional &slot = _colors[rgba.cgbColor()]; - if (rgba.cgbColor() == Rgba::transparent) { + if (rgba.cgbColor() == Rgba::transparent && !isBgColorTransparent()) { options.hasTransparentPixels = true; } @@ -519,10 +523,12 @@ struct AttrmapEntry { bool yFlip; bool xFlip; - static constexpr decltype(protoPaletteID) transparent = SIZE_MAX; + static constexpr size_t transparent = static_cast(-1); + static constexpr size_t background = static_cast(-2); + bool isBackgroundTile() const { return protoPaletteID == background; } size_t getPalID(DefaultInitVec const &mappings) const { - return protoPaletteID == transparent ? 0 : mappings[protoPaletteID]; + return mappings[isBackgroundTile() || protoPaletteID == transparent ? 0 : protoPaletteID]; } }; @@ -852,13 +858,16 @@ static void outputUnoptimizedTileData( remainingTiles -= options.trim; for (auto [tile, attr] : zip(png.visitAsTiles(), attrmap)) { - // If the tile is fully transparent, default to palette 0 - Palette const &palette = palettes[attr.getPalID(mappings)]; - for (uint32_t y = 0; y < 8; ++y) { - uint16_t bitplanes = TileData::rowBitplanes(tile, palette, y); - output->sputc(bitplanes & 0xFF); - if (options.bitDepth == 2) { - output->sputc(bitplanes >> 8); + // Do not emit fully-background tiles. + if (!attr.isBackgroundTile()) { + // If the tile is fully transparent, this defaults to palette 0. + Palette const &palette = palettes[attr.getPalID(mappings)]; + for (uint32_t y = 0; y < 8; ++y) { + uint16_t bitplanes = TileData::rowBitplanes(tile, palette, y); + output->sputc(bitplanes & 0xFF); + if (options.bitDepth == 2) { + output->sputc(bitplanes >> 8); + } } } @@ -898,16 +907,21 @@ static void outputUnoptimizedMaps( } if (tilemapOutput.has_value()) { - (*tilemapOutput)->sputc(tileID + options.baseTileIDs[bank]); + (*tilemapOutput) + ->sputc((attr.isBackgroundTile() ? 0 : tileID) + options.baseTileIDs[bank]); } + uint8_t palID = attr.getPalID(mappings); if (attrmapOutput.has_value()) { - uint8_t palID = attr.getPalID(mappings) & 7; - (*attrmapOutput)->sputc(palID | bank << 3); // The other flags are all 0 + (*attrmapOutput)->sputc((palID & 7) | bank << 3); // The other flags are all 0 } if (palmapOutput.has_value()) { - (*palmapOutput)->sputc(attr.getPalID(mappings)); + (*palmapOutput)->sputc(palID); + } + + // Background tiles are skipped in the tile data, so they should be skipped in the maps too. + if (!attr.isBackgroundTile()) { + ++tileID; } - ++tileID; } } @@ -1000,22 +1014,30 @@ static UniqueTiles dedupTiles( bool inputWithoutOutput = !options.inputTileset.empty() && options.output.empty(); for (auto [tile, attr] : zip(png.visitAsTiles(), attrmap)) { - auto [tileID, matchType] = tiles.addTile({tile, palettes[mappings[attr.protoPaletteID]]}); + if (attr.isBackgroundTile()) { + attr.xFlip = false; + attr.yFlip = false; + attr.bank = 0; + attr.tileID = 0; + } else { + auto [tileID, matchType] = + tiles.addTile({tile, palettes[mappings[attr.protoPaletteID]]}); - if (inputWithoutOutput && matchType == TileData::NOPE) { - error( - "Tile at (%" PRIu32 ", %" PRIu32 - ") is not within the input tileset, and `-o` was not given!", - tile.x, - tile.y - ); + if (inputWithoutOutput && matchType == TileData::NOPE) { + error( + "Tile at (%" PRIu32 ", %" PRIu32 + ") is not within the input tileset, and `-o` was not given!", + tile.x, + tile.y + ); + } + + attr.xFlip = matchType == TileData::HFLIP || matchType == TileData::VHFLIP; + attr.yFlip = matchType == TileData::VFLIP || matchType == TileData::VHFLIP; + attr.bank = tileID >= options.maxNbTiles[0]; + attr.tileID = (attr.bank ? tileID - options.maxNbTiles[0] : tileID) + + options.baseTileIDs[attr.bank]; } - - attr.xFlip = matchType == TileData::HFLIP || matchType == TileData::VHFLIP; - attr.yFlip = matchType == TileData::VFLIP || matchType == TileData::VHFLIP; - attr.bank = tileID >= options.maxNbTiles[0]; - attr.tileID = - (attr.bank ? tileID - options.maxNbTiles[0] : tileID) + options.baseTileIDs[attr.bank]; } // Copy elision should prevent the contained `unordered_set` from being re-constructed @@ -1139,7 +1161,8 @@ void process() { std::unordered_set tileColors; for (uint32_t y = 0; y < 8; ++y) { for (uint32_t x = 0; x < 8; ++x) { - if (Rgba color = tile.pixel(x, y); !color.isTransparent()) { + if (Rgba color = tile.pixel(x, y); + !color.isTransparent() || !options.hasTransparentPixels) { tileColors.insert(color.cgbColor()); } } @@ -1157,6 +1180,7 @@ void process() { if (tileColors.empty()) { // "Empty" proto-palettes screw with the packing process, so discard those + assume(!isBgColorTransparent()); attrs.protoPaletteID = AttrmapEntry::transparent; continue; } @@ -1166,6 +1190,21 @@ void process() { protoPalette.add(cgbColor); } + if (options.bgColor.has_value() + && std::find(RANGE(tileColors), options.bgColor->cgbColor()) != tileColors.end()) { + if (tileColors.size() == 1) { + // The tile contains just the background color, skip it. + attrs.protoPaletteID = AttrmapEntry::background; + continue; + } + fatal( + "Tile (%" PRIu32 ", %" PRIu32 ") contains the background color (#%08x)!", + tile.x, + tile.y, + options.bgColor->toCSS() + ); + } + // Insert the proto-palette, making sure to avoid overlaps for (size_t n = 0; n < protoPalettes.size(); ++n) { switch (protoPalette.compare(protoPalettes[n])) { @@ -1197,7 +1236,7 @@ void process() { } attrs.protoPaletteID = protoPalettes.size(); - if (protoPalettes.size() == AttrmapEntry::transparent) { // Check for overflow + if (protoPalettes.size() == AttrmapEntry::background) { // Check for overflow fatal( "Reached %zu proto-palettes... sorry, this image is too much for me to handle :(", AttrmapEntry::transparent diff --git a/test/gfx/bg_fuse.err b/test/gfx/bg_fuse.err new file mode 100644 index 00000000..ecbbd45c --- /dev/null +++ b/test/gfx/bg_fuse.err @@ -0,0 +1,3 @@ +warning: Fusing colors #a9b9c9ff and #aabbccff into Game Boy color $66f5 [first seen at x: 1, y: 1] +FATAL: Tile (0, 0) contains the background color (#aabbccff)! +Conversion aborted after 1 error diff --git a/test/gfx/bg_fuse.flags b/test/gfx/bg_fuse.flags new file mode 100644 index 00000000..aba6c76b --- /dev/null +++ b/test/gfx/bg_fuse.flags @@ -0,0 +1 @@ +-B #aBc diff --git a/test/gfx/bg_fuse.png b/test/gfx/bg_fuse.png new file mode 100644 index 0000000000000000000000000000000000000000..2fee135f8b779cb32f9a732c69a525b78302cab1 GIT binary patch literal 177 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjEX7WqAsj$Z!;#VfSX$hrb71N|Pex~ZJ~v$jjt|R}yu5>(#0(4q;^cY$T1ae7c%Yk*vY>D?D}#Z& WxMACu=?y?T89ZJ6T-G@yGywo@J2c_| literal 0 HcmV?d00001 diff --git a/test/gfx/bg_in_tile.err b/test/gfx/bg_in_tile.err new file mode 100644 index 00000000..91fa5779 --- /dev/null +++ b/test/gfx/bg_in_tile.err @@ -0,0 +1,2 @@ +FATAL: Tile (16, 0) contains the background color (#ffd800ff)! +Conversion aborted after 1 error diff --git a/test/gfx/bg_in_tile.flags b/test/gfx/bg_in_tile.flags new file mode 100644 index 00000000..569f005c --- /dev/null +++ b/test/gfx/bg_in_tile.flags @@ -0,0 +1 @@ +-B #ffd800 diff --git a/test/gfx/bg_in_tile.png b/test/gfx/bg_in_tile.png new file mode 100644 index 0000000000000000000000000000000000000000..3d7fc083c12480616a6a38b3e955dab3f6b76fc5 GIT binary patch literal 181 zcmeAS@N?(olHy`uVBq!ia0vp^5jQWH$3HLf+n)Lp-g2&mA+pKOElR*es~0D%~2zZ}i}q cyGS8(cc?6L)49dXKzkWHUHx3vIVCg!0Q$8(Sk-X7VSU5@|Em~68P+q@GyDfb28IU@DkT1}eB=7f`jz`P>wTb_ t`wjUH_6l;6?CcCsRsR^ja;*vtUG@L}SMJxMp{rMgg{;{T@!$ai0{}<5DN+Cc literal 0 HcmV?d00001 diff --git a/test/gfx/bg_oval.out.attrmap b/test/gfx/bg_oval.out.attrmap new file mode 100644 index 0000000000000000000000000000000000000000..bc8840b22b1bcf5594717cc39cf105953e00c24d GIT binary patch literal 9 KcmZQzfC2yj2><~A literal 0 HcmV?d00001 diff --git a/test/gfx/bg_oval.out.pal b/test/gfx/bg_oval.out.pal new file mode 100644 index 0000000000000000000000000000000000000000..0ef9254a0c759df4203f038735fd80ffe91ae907 GIT binary patch literal 8 PcmexgzctKDlYs#M69@wR literal 0 HcmV?d00001 diff --git a/test/gfx/bg_oval.out.tilemap b/test/gfx/bg_oval.out.tilemap new file mode 100644 index 0000000000000000000000000000000000000000..344d9e9a0433a91ca61bb06827ee353dc7813e06 GIT binary patch literal 9 QcmZQzU}R!oW?^Lj002G!5C8xG literal 0 HcmV?d00001 diff --git a/test/gfx/bg_oval.png b/test/gfx/bg_oval.png new file mode 100644 index 0000000000000000000000000000000000000000..465d093e83dd09eeac1dc80c6733f5884ba1fec2 GIT binary patch literal 334 zcmV-U0kQsxP)06;nDY*Oigh=3&A9jbciANSxt!2uvuAoy$K zPS9a+hkXL02CM1Ihx_zhyVZp07*qoM6N<$g3*|WWB>pF literal 0 HcmV?d00001 diff --git a/test/gfx/input_tileset_with_bg.flags b/test/gfx/input_tileset_with_bg.flags new file mode 100644 index 00000000..6b93b160 --- /dev/null +++ b/test/gfx/input_tileset_with_bg.flags @@ -0,0 +1,4 @@ +-B #fff +-i input_tileset_with_bg.in.2bpp +-c gbc:input_tileset_with_bg.in.pal +-u diff --git a/test/gfx/input_tileset_with_bg.in.2bpp b/test/gfx/input_tileset_with_bg.in.2bpp new file mode 100644 index 0000000000000000000000000000000000000000..7116e54f85eaaff14744718e64838b508d0e73ac GIT binary patch literal 64 hcmZSh&wv6!oWnK{2x4P^|8Zme6Zrb_Idu~MpG@5R6Ua zd%+r;@?`D#CZ-IQ+{Uz7htnQ#@-Z3QF4t&C+07z;sVUCy0K>d*F+ne+wiP{!w^Uep zRZODcn-_24g6&;CRTWA~Ms0n>#$&jT8GLZcI;)#u)O=umP$pJCpl zf6{FXHpNP{Y|9vIij?j)u2lSTqBGy(!AxQ4EO)WAq>g?)pbr>4UHx3vIVCg!014t> ALI3~& literal 0 HcmV?d00001 diff --git a/test/gfx/trans_bg_in_tile.err b/test/gfx/trans_bg_in_tile.err new file mode 100644 index 00000000..ce6bf37e --- /dev/null +++ b/test/gfx/trans_bg_in_tile.err @@ -0,0 +1,2 @@ +FATAL: Tile (16, 0) contains the background color (#00000000)! +Conversion aborted after 1 error diff --git a/test/gfx/trans_bg_in_tile.flags b/test/gfx/trans_bg_in_tile.flags new file mode 100644 index 00000000..45911e3e --- /dev/null +++ b/test/gfx/trans_bg_in_tile.flags @@ -0,0 +1 @@ +-B transparent diff --git a/test/gfx/trans_bg_in_tile.png b/test/gfx/trans_bg_in_tile.png new file mode 100644 index 0000000000000000000000000000000000000000..d56baad0aa0b86f508091d94cab88eec701cc7dd GIT binary patch literal 179 zcmeAS@N?(olHy`uVBq!ia0vp^5Fxax8{p=Jg~yZpy@I==4sdACi5!RM4h zi*QBr4avS`yt`VsqI3UWSrZ?ap=07@HC6m;F57BP=@nO3wEdT2Ic|C_;ljtDl85O| at=uQLBo?#CXf^|FW$<+Mb6Mw<&;$UocRkhs literal 0 HcmV?d00001