diff --git a/include/gfx/main.hpp b/include/gfx/main.hpp index 7a030917..9851db54 100644 --- a/include/gfx/main.hpp +++ b/include/gfx/main.hpp @@ -39,7 +39,12 @@ struct Options { } palSpecType = NO_SPEC; // -c std::vector> palSpec{}; uint8_t bitDepth = 2; // -d - std::array inputSlice{0, 0, 0, 0}; // -L (margins in clockwise order, like CSS) + struct { + uint16_t left; + uint16_t top; + uint16_t width; + uint16_t height; + } inputSlice{0, 0, 0, 0}; // -L (margins in clockwise order, like CSS) std::array maxNbTiles{UINT16_MAX, 0}; // -N uint8_t nbPalettes = 8; // -n std::string output{}; // -o diff --git a/man/rgbgfx.1 b/man/rgbgfx.1 index 7aeaf5a9..25d72b07 100644 --- a/man/rgbgfx.1 +++ b/man/rgbgfx.1 @@ -157,8 +157,16 @@ Set the bit depth of the output tile data, in bits per pixel (bpp), either 1 or This changes how tile data is output, and the maximum number of colors per palette (2 and 4 respectively). .It Fl L Ar slice , Fl Fl slice Ar slice Only process a given rectangle of the image. -.Sy TODO: arg format . -This is useful for example if the input image is a sheet of some sort, and you want to convert each item individually. +This is useful for example if the input image is a sheet of some sort, and you want to convert each cel individually. +The default is to process the whole image as-is. +.Pp +.Ar slice +must be two number pairs, separated by a colon. +The numbers must be separated by commas; space is allowed around all punctuation. +The first number pair specifies the X and Y coordinates of the top-left pixel that will be processed (anything above it or to its left will be ignored). +The second number pair specifies how many tiles to process horizontally and vertically, respectively. +.Pp +.Sy Fl L Sy is ignored in reverse mode , No no padding is inserted . .It Fl m , Fl Fl mirror-tiles Deduplicate tiles that are mirrors of each other. Tiles are checked for horizontal, vertical, and horizontal-vertical mirroring. diff --git a/src/gfx/main.cpp b/src/gfx/main.cpp index cacb8553..71caf08d 100644 --- a/src/gfx/main.cpp +++ b/src/gfx/main.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -394,7 +395,44 @@ static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilem } break; case 'L': - options.inputSlice = {0, 0, 0, 0}; // TODO + options.inputSlice.left = parseNumber(arg, "Input slice left coordinate"); + if (options.inputSlice.left > INT16_MAX) { + error("Input slice left coordinate is out of range!"); + break; + } + skipWhitespace(arg); + if (*arg != ',') { + error("Missing comma after left coordinate in \"%s\"", musl_optarg); + break; + } + ++arg; + skipWhitespace(arg); + options.inputSlice.top = parseNumber(arg, "Input slice upper coordinate"); + skipWhitespace(arg); + if (*arg != ':') { + error("Missing colon after upper coordinate in \"%s\"", musl_optarg); + break; + } + ++arg; + skipWhitespace(arg); + options.inputSlice.width = parseNumber(arg, "Input slice width"); + skipWhitespace(arg); + if (options.inputSlice.width == 0) { + error("Input slice width may not be 0!"); + } + if (*arg != ',') { + error("Missing comma after width in \"%s\"", musl_optarg); + break; + } + ++arg; + skipWhitespace(arg); + options.inputSlice.height = parseNumber(arg, "Input slice height"); + if (options.inputSlice.height == 0) { + error("Input slice height may not be 0!"); + } + if (*arg != '\0') { + error("Unexpected extra characters after slice spec in \"%s\"", musl_optarg); + } break; case 'm': options.allowMirroring = true; @@ -695,10 +733,10 @@ int main(int argc, char *argv[]) { fprintf(stderr, "\tDedup unit: %" PRIu16 "x%" PRIu16 " tiles\n", options.unitSize[0], options.unitSize[1]); fprintf(stderr, - "\tInput image slice: %" PRIu32 "x%" PRIu32 " pixels from (%" PRIu32 ", %" PRIu32 - ")\n", - options.inputSlice[2], options.inputSlice[3], options.inputSlice[0], - options.inputSlice[1]); + "\tInput image slice: %" PRIu32 "x%" PRIu32 " pixels starting at (%" PRIi32 + ", %" PRIi32 ")\n", + options.inputSlice.width, options.inputSlice.height, options.inputSlice.left, + options.inputSlice.top); fprintf(stderr, "\tBase tile IDs: [%" PRIu8 ", %" PRIu8 "]\n", options.baseTileIDs[0], options.baseTileIDs[1]); fprintf(stderr, "\tMaximum %" PRIu16 " tiles in bank 0, %" PRIu16 " in bank 1\n", diff --git a/src/gfx/process.cpp b/src/gfx/process.cpp index 2b615a6e..bc49966f 100644 --- a/src/gfx/process.cpp +++ b/src/gfx/process.cpp @@ -218,10 +218,10 @@ public: png_get_IHDR(png, info, &width, &height, &bitDepth, &colorType, &interlaceType, nullptr, nullptr); - if (width % 8 != 0) { + if (options.inputSlice.width == 0 && width % 8 != 0) { fatal("Image width (%" PRIu32 " pixels) is not a multiple of 8!", width); } - if (height % 8 != 0) { + if (options.inputSlice.height == 0 && height % 8 != 0) { fatal("Image height (%" PRIu32 " pixels) is not a multiple of 8!", height); } @@ -424,8 +424,12 @@ public: uint32_t const limit; uint32_t x, y; - std::pair coords() const { return {x, y}; } - Tile operator*() const { return {parent._png, x, y}; } + std::pair coords() const { + return {x + options.inputSlice.left, y + options.inputSlice.top}; + } + Tile operator*() const { + return {parent._png, x + options.inputSlice.left, y + options.inputSlice.top}; + } iterator &operator++() { auto [major, minor] = parent._columnMajor ? std::tie(y, x) : std::tie(x, y); @@ -455,7 +459,8 @@ public: }; public: TilesVisitor visitAsTiles(bool columnMajor) const { - return {*this, columnMajor, width, height}; + return {*this, columnMajor, options.inputSlice.width ? options.inputSlice.width * 8 : width, + options.inputSlice.height ? options.inputSlice.height * 8 : height}; } }; @@ -1010,7 +1015,8 @@ void process() { attrs.protoPaletteID = protoPalettes.size(); if (protoPalettes.size() == AttrmapEntry::transparent) { // Check for overflow - fatal("Reached %zu proto-palettes... sorry, this image is too much for me to handle :(", AttrmapEntry::transparent); + fatal("Reached %zu proto-palettes... sorry, this image is too much for me to handle :(", + AttrmapEntry::transparent); } protoPalettes.push_back(tileColors); contained:; diff --git a/src/gfx/reverse.cpp b/src/gfx/reverse.cpp index 4ffe1b1c..5b871c93 100644 --- a/src/gfx/reverse.cpp +++ b/src/gfx/reverse.cpp @@ -91,6 +91,16 @@ void reverse() { warning("The color curve is not yet supported in reverse mode..."); } + if (options.inputSlice.left != 0 || options.inputSlice.top != 0 + || options.inputSlice.height != 0) { + warning("\"Sliced-off\" pixels are ignored in reverse mode"); + } + if (options.inputSlice.width != 0 && options.inputSlice.width != options.reversedWidth * 8) { + warning("Specified input slice width (%" PRIu16 + ") doesn't match provided reversing width (%" PRIu8 " * 8)", + options.inputSlice.width, options.reversedWidth); + } + options.verbosePrint(Options::VERB_LOG_ACT, "Reading tiles...\n"); auto const tiles = readInto(options.output); uint8_t tileSize = 8 * options.bitDepth; @@ -115,14 +125,7 @@ void reverse() { options.maxNbTiles[0], options.maxNbTiles[1]); } - size_t width, height; - size_t usefulWidth = options.reversedWidth - options.inputSlice[1] - options.inputSlice[3]; - if (usefulWidth % 8 != 0) { - fatal( - "No input slice specified (`-L`), and specified image width (%zu) not a multiple of 8", - usefulWidth); - } - width = usefulWidth / 8; + size_t width = options.reversedWidth, height; // In tiles if (nbTileInstances % width != 0) { fatal("Total number of tiles read (%zu) cannot be divided by image width (%zu tiles)", nbTileInstances, width); @@ -207,10 +210,8 @@ void reverse() { png_set_write_fn(png, &pngFile, writePng, flushPng); // TODO: if `-f` is passed, write the image indexed instead of RGB - png_set_IHDR(png, pngInfo, options.reversedWidth, - height * 8 + options.inputSlice[0] + options.inputSlice[2], 8, - PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, - PNG_FILTER_TYPE_DEFAULT); + png_set_IHDR(png, pngInfo, options.reversedWidth * 8, height * 8, 8, PNG_COLOR_TYPE_RGB_ALPHA, + PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); png_write_info(png, pngInfo); png_color_8 sbitChunk; @@ -221,26 +222,15 @@ void reverse() { png_set_sBIT(png, pngInfo, &sbitChunk); constexpr uint8_t SIZEOF_PIXEL = 4; // Each pixel is 4 bytes (RGBA @ 8 bits/component) - size_t const SIZEOF_ROW = options.reversedWidth * SIZEOF_PIXEL; + size_t const SIZEOF_ROW = options.reversedWidth * 8 * SIZEOF_PIXEL; std::vector tileRow(8 * SIZEOF_ROW, 0xFF); // Data for 8 rows of pixels uint8_t * const rowPtrs[8] = { - &tileRow.data()[0 * SIZEOF_ROW + options.inputSlice[3]], - &tileRow.data()[1 * SIZEOF_ROW + options.inputSlice[3]], - &tileRow.data()[2 * SIZEOF_ROW + options.inputSlice[3]], - &tileRow.data()[3 * SIZEOF_ROW + options.inputSlice[3]], - &tileRow.data()[4 * SIZEOF_ROW + options.inputSlice[3]], - &tileRow.data()[5 * SIZEOF_ROW + options.inputSlice[3]], - &tileRow.data()[6 * SIZEOF_ROW + options.inputSlice[3]], - &tileRow.data()[7 * SIZEOF_ROW + options.inputSlice[3]], + &tileRow.data()[0 * SIZEOF_ROW], &tileRow.data()[1 * SIZEOF_ROW], + &tileRow.data()[2 * SIZEOF_ROW], &tileRow.data()[3 * SIZEOF_ROW], + &tileRow.data()[4 * SIZEOF_ROW], &tileRow.data()[5 * SIZEOF_ROW], + &tileRow.data()[6 * SIZEOF_ROW], &tileRow.data()[7 * SIZEOF_ROW], }; - auto const fillRows = [&png, &tileRow](size_t nbRows) { - for (size_t _ = 0; _ < nbRows; ++_) { - png_write_row(png, tileRow.data()); - } - }; - fillRows(options.inputSlice[0]); - for (size_t ty = 0; ty < height; ++ty) { for (size_t tx = 0; tx < width; ++tx) { size_t index = options.columnMajor ? ty + tx * width : ty * width + tx; @@ -295,9 +285,6 @@ void reverse() { // pointed-to data) png_write_rows(png, const_cast(rowPtrs), 8); } - // Clear the first row again for the function - std::fill(tileRow.begin(), tileRow.begin() + SIZEOF_ROW, 0xFF); - fillRows(options.inputSlice[2]); // Finalize the write png_write_end(png, pngInfo); diff --git a/test/gfx/crop.flags b/test/gfx/crop.flags new file mode 100644 index 00000000..750c1226 --- /dev/null +++ b/test/gfx/crop.flags @@ -0,0 +1 @@ +-L 2,1:1,1 diff --git a/test/gfx/crop.png b/test/gfx/crop.png new file mode 100644 index 00000000..30346cc3 Binary files /dev/null and b/test/gfx/crop.png differ diff --git a/test/gfx/rgbgfx_test.cpp b/test/gfx/rgbgfx_test.cpp index 641a4cd0..a7dbb3da 100644 --- a/test/gfx/rgbgfx_test.cpp +++ b/test/gfx/rgbgfx_test.cpp @@ -404,7 +404,7 @@ int main(int argc, char **argv) { char path[] = "../../rgbgfx", reverse_opt[] = "-r", out_opt[] = "-o", out_file[] = "result.2bpp", pal_opt[] = "-p", pal_file[] = "result.pal", attr_opt[] = "-a", attr_file[] = "result.attrmap", in_file[] = "result.png"; - auto width_string = std::to_string(image0.getWidth()); + auto width_string = std::to_string(image0.getWidth() / 8); std::vector args = { path, reverse_opt, width_string.data(), out_opt, out_file, pal_opt, pal_file, attr_opt, attr_file, in_file};