mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 18:22:07 +00:00
Implement slicing input image
This commit is contained in:
@@ -39,7 +39,12 @@ struct Options {
|
|||||||
} palSpecType = NO_SPEC; // -c
|
} palSpecType = NO_SPEC; // -c
|
||||||
std::vector<std::array<Rgba, 4>> palSpec{};
|
std::vector<std::array<Rgba, 4>> palSpec{};
|
||||||
uint8_t bitDepth = 2; // -d
|
uint8_t bitDepth = 2; // -d
|
||||||
std::array<uint32_t, 4> 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<uint16_t, 2> maxNbTiles{UINT16_MAX, 0}; // -N
|
std::array<uint16_t, 2> maxNbTiles{UINT16_MAX, 0}; // -N
|
||||||
uint8_t nbPalettes = 8; // -n
|
uint8_t nbPalettes = 8; // -n
|
||||||
std::string output{}; // -o
|
std::string output{}; // -o
|
||||||
|
|||||||
12
man/rgbgfx.1
12
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).
|
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
|
.It Fl L Ar slice , Fl Fl slice Ar slice
|
||||||
Only process a given rectangle of the image.
|
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 cel individually.
|
||||||
This is useful for example if the input image is a sheet of some sort, and you want to convert each item 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
|
.It Fl m , Fl Fl mirror-tiles
|
||||||
Deduplicate tiles that are mirrors of each other.
|
Deduplicate tiles that are mirrors of each other.
|
||||||
Tiles are checked for horizontal, vertical, and horizontal-vertical mirroring.
|
Tiles are checked for horizontal, vertical, and horizontal-vertical mirroring.
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
|
#include <cstdint>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <ios>
|
#include <ios>
|
||||||
@@ -394,7 +395,44 @@ static char *parseArgv(int argc, char **argv, bool &autoAttrmap, bool &autoTilem
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'L':
|
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;
|
break;
|
||||||
case 'm':
|
case 'm':
|
||||||
options.allowMirroring = true;
|
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],
|
fprintf(stderr, "\tDedup unit: %" PRIu16 "x%" PRIu16 " tiles\n", options.unitSize[0],
|
||||||
options.unitSize[1]);
|
options.unitSize[1]);
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"\tInput image slice: %" PRIu32 "x%" PRIu32 " pixels from (%" PRIu32 ", %" PRIu32
|
"\tInput image slice: %" PRIu32 "x%" PRIu32 " pixels starting at (%" PRIi32
|
||||||
")\n",
|
", %" PRIi32 ")\n",
|
||||||
options.inputSlice[2], options.inputSlice[3], options.inputSlice[0],
|
options.inputSlice.width, options.inputSlice.height, options.inputSlice.left,
|
||||||
options.inputSlice[1]);
|
options.inputSlice.top);
|
||||||
fprintf(stderr, "\tBase tile IDs: [%" PRIu8 ", %" PRIu8 "]\n", options.baseTileIDs[0],
|
fprintf(stderr, "\tBase tile IDs: [%" PRIu8 ", %" PRIu8 "]\n", options.baseTileIDs[0],
|
||||||
options.baseTileIDs[1]);
|
options.baseTileIDs[1]);
|
||||||
fprintf(stderr, "\tMaximum %" PRIu16 " tiles in bank 0, %" PRIu16 " in bank 1\n",
|
fprintf(stderr, "\tMaximum %" PRIu16 " tiles in bank 0, %" PRIu16 " in bank 1\n",
|
||||||
|
|||||||
@@ -218,10 +218,10 @@ public:
|
|||||||
png_get_IHDR(png, info, &width, &height, &bitDepth, &colorType, &interlaceType, nullptr,
|
png_get_IHDR(png, info, &width, &height, &bitDepth, &colorType, &interlaceType, nullptr,
|
||||||
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);
|
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);
|
fatal("Image height (%" PRIu32 " pixels) is not a multiple of 8!", height);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -424,8 +424,12 @@ public:
|
|||||||
uint32_t const limit;
|
uint32_t const limit;
|
||||||
uint32_t x, y;
|
uint32_t x, y;
|
||||||
|
|
||||||
std::pair<uint32_t, uint32_t> coords() const { return {x, y}; }
|
std::pair<uint32_t, uint32_t> coords() const {
|
||||||
Tile operator*() const { return {parent._png, x, y}; }
|
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++() {
|
iterator &operator++() {
|
||||||
auto [major, minor] = parent._columnMajor ? std::tie(y, x) : std::tie(x, y);
|
auto [major, minor] = parent._columnMajor ? std::tie(y, x) : std::tie(x, y);
|
||||||
@@ -455,7 +459,8 @@ public:
|
|||||||
};
|
};
|
||||||
public:
|
public:
|
||||||
TilesVisitor visitAsTiles(bool columnMajor) const {
|
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();
|
attrs.protoPaletteID = protoPalettes.size();
|
||||||
if (protoPalettes.size() == AttrmapEntry::transparent) { // Check for overflow
|
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);
|
protoPalettes.push_back(tileColors);
|
||||||
contained:;
|
contained:;
|
||||||
|
|||||||
@@ -91,6 +91,16 @@ void reverse() {
|
|||||||
warning("The color curve is not yet supported in reverse mode...");
|
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");
|
options.verbosePrint(Options::VERB_LOG_ACT, "Reading tiles...\n");
|
||||||
auto const tiles = readInto(options.output);
|
auto const tiles = readInto(options.output);
|
||||||
uint8_t tileSize = 8 * options.bitDepth;
|
uint8_t tileSize = 8 * options.bitDepth;
|
||||||
@@ -115,14 +125,7 @@ void reverse() {
|
|||||||
options.maxNbTiles[0], options.maxNbTiles[1]);
|
options.maxNbTiles[0], options.maxNbTiles[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t width, height;
|
size_t width = options.reversedWidth, height; // In tiles
|
||||||
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;
|
|
||||||
if (nbTileInstances % width != 0) {
|
if (nbTileInstances % width != 0) {
|
||||||
fatal("Total number of tiles read (%zu) cannot be divided by image width (%zu tiles)",
|
fatal("Total number of tiles read (%zu) cannot be divided by image width (%zu tiles)",
|
||||||
nbTileInstances, width);
|
nbTileInstances, width);
|
||||||
@@ -207,10 +210,8 @@ void reverse() {
|
|||||||
png_set_write_fn(png, &pngFile, writePng, flushPng);
|
png_set_write_fn(png, &pngFile, writePng, flushPng);
|
||||||
|
|
||||||
// TODO: if `-f` is passed, write the image indexed instead of RGB
|
// TODO: if `-f` is passed, write the image indexed instead of RGB
|
||||||
png_set_IHDR(png, pngInfo, options.reversedWidth,
|
png_set_IHDR(png, pngInfo, options.reversedWidth * 8, height * 8, 8, PNG_COLOR_TYPE_RGB_ALPHA,
|
||||||
height * 8 + options.inputSlice[0] + options.inputSlice[2], 8,
|
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
|
||||||
PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
|
|
||||||
PNG_FILTER_TYPE_DEFAULT);
|
|
||||||
png_write_info(png, pngInfo);
|
png_write_info(png, pngInfo);
|
||||||
|
|
||||||
png_color_8 sbitChunk;
|
png_color_8 sbitChunk;
|
||||||
@@ -221,26 +222,15 @@ void reverse() {
|
|||||||
png_set_sBIT(png, pngInfo, &sbitChunk);
|
png_set_sBIT(png, pngInfo, &sbitChunk);
|
||||||
|
|
||||||
constexpr uint8_t SIZEOF_PIXEL = 4; // Each pixel is 4 bytes (RGBA @ 8 bits/component)
|
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<uint8_t> tileRow(8 * SIZEOF_ROW, 0xFF); // Data for 8 rows of pixels
|
std::vector<uint8_t> tileRow(8 * SIZEOF_ROW, 0xFF); // Data for 8 rows of pixels
|
||||||
uint8_t * const rowPtrs[8] = {
|
uint8_t * const rowPtrs[8] = {
|
||||||
&tileRow.data()[0 * SIZEOF_ROW + options.inputSlice[3]],
|
&tileRow.data()[0 * SIZEOF_ROW], &tileRow.data()[1 * SIZEOF_ROW],
|
||||||
&tileRow.data()[1 * SIZEOF_ROW + options.inputSlice[3]],
|
&tileRow.data()[2 * SIZEOF_ROW], &tileRow.data()[3 * SIZEOF_ROW],
|
||||||
&tileRow.data()[2 * SIZEOF_ROW + options.inputSlice[3]],
|
&tileRow.data()[4 * SIZEOF_ROW], &tileRow.data()[5 * SIZEOF_ROW],
|
||||||
&tileRow.data()[3 * SIZEOF_ROW + options.inputSlice[3]],
|
&tileRow.data()[6 * SIZEOF_ROW], &tileRow.data()[7 * SIZEOF_ROW],
|
||||||
&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]],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
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 ty = 0; ty < height; ++ty) {
|
||||||
for (size_t tx = 0; tx < width; ++tx) {
|
for (size_t tx = 0; tx < width; ++tx) {
|
||||||
size_t index = options.columnMajor ? ty + tx * width : ty * width + tx;
|
size_t index = options.columnMajor ? ty + tx * width : ty * width + tx;
|
||||||
@@ -295,9 +285,6 @@ void reverse() {
|
|||||||
// pointed-to data)
|
// pointed-to data)
|
||||||
png_write_rows(png, const_cast<png_bytepp>(rowPtrs), 8);
|
png_write_rows(png, const_cast<png_bytepp>(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
|
// Finalize the write
|
||||||
png_write_end(png, pngInfo);
|
png_write_end(png, pngInfo);
|
||||||
|
|||||||
1
test/gfx/crop.flags
Normal file
1
test/gfx/crop.flags
Normal file
@@ -0,0 +1 @@
|
|||||||
|
-L 2,1:1,1
|
||||||
BIN
test/gfx/crop.png
Normal file
BIN
test/gfx/crop.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 671 B |
@@ -404,7 +404,7 @@ int main(int argc, char **argv) {
|
|||||||
char path[] = "../../rgbgfx", reverse_opt[] = "-r", out_opt[] = "-o",
|
char path[] = "../../rgbgfx", reverse_opt[] = "-r", out_opt[] = "-o",
|
||||||
out_file[] = "result.2bpp", pal_opt[] = "-p", pal_file[] = "result.pal",
|
out_file[] = "result.2bpp", pal_opt[] = "-p", pal_file[] = "result.pal",
|
||||||
attr_opt[] = "-a", attr_file[] = "result.attrmap", in_file[] = "result.png";
|
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<char *> args = {
|
std::vector<char *> args = {
|
||||||
path, reverse_opt, width_string.data(), out_opt, out_file, pal_opt,
|
path, reverse_opt, width_string.data(), out_opt, out_file, pal_opt,
|
||||||
pal_file, attr_opt, attr_file, in_file};
|
pal_file, attr_opt, attr_file, in_file};
|
||||||
|
|||||||
Reference in New Issue
Block a user