Skip to content

Commit 2dc533b

Browse files
havesscopybara-github
authored andcommitted
Allow flipping textures with any number of channels and user provided pixel data.
PiperOrigin-RevId: 813256285 Change-Id: I0d49885dc483ab8f093b2566ec4bef355575505e
1 parent bdccbcc commit 2dc533b

File tree

3 files changed

+128
-94
lines changed

3 files changed

+128
-94
lines changed

src/user/user_objects.cc

Lines changed: 67 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <cstddef>
2121
#include <cstdint>
2222
#include <cstdio>
23+
#include <cstdlib>
2324
#include <cstring>
2425
#include <deque>
2526
#include <functional>
@@ -59,8 +60,9 @@ class PNGImage {
5960
int Height() const { return height_; }
6061
bool IsSRGB() const { return is_srgb_; }
6162

62-
uint8_t operator[] (int i) const { return data_[i]; }
63-
std::vector<unsigned char>& MoveData() { return data_; }
63+
std::byte operator[] (int i) const { return data_[i]; }
64+
65+
mjByteVec&& MoveData() && { return std::move(data_); }
6466

6567
private:
6668
std::size_t Size() const {
@@ -71,7 +73,7 @@ class PNGImage {
7173
int height_;
7274
bool is_srgb_;
7375
LodePNGColorType color_type_;
74-
std::vector<uint8_t> data_;
76+
mjByteVec data_;
7577
};
7678

7779
PNGImage PNGImage::Load(const mjCBase* obj, mjResource* resource,
@@ -113,7 +115,12 @@ PNGImage PNGImage::Load(const mjCBase* obj, mjResource* resource,
113115
lodepng::State state;
114116
state.info_raw.colortype = image.color_type_;
115117
state.info_raw.bitdepth = 8;
116-
unsigned err = lodepng::decode(image.data_, w, h, state, buffer, nbuffer);
118+
unsigned char* data_ptr = nullptr;
119+
unsigned err = lodepng_decode(&data_ptr, &w, &h, &state, buffer, nbuffer);
120+
struct free_delete {
121+
void operator()(unsigned char* ptr) const { std::free(ptr); }
122+
};
123+
std::unique_ptr<unsigned char, free_delete> data{data_ptr};
117124

118125
// check for errors
119126
if (err) {
@@ -122,6 +129,13 @@ PNGImage PNGImage::Load(const mjCBase* obj, mjResource* resource,
122129
throw mjCError(obj, "%s", ss.str().c_str());
123130
}
124131

132+
if (data) {
133+
size_t buffersize = lodepng_get_raw_size(w, h, &state.info_raw);
134+
image.data_.insert(image.data_.end(),
135+
reinterpret_cast<std::byte*>(data.get()),
136+
reinterpret_cast<std::byte*>(&data.get()[buffersize]));
137+
}
138+
125139
image.width_ = w;
126140
image.height_ = h;
127141
image.is_srgb_ = (state.info_png.srgb_defined == 1);
@@ -4890,7 +4904,7 @@ void mjCTexture::BuiltinCube(void) {
48904904

48914905
// load PNG file
48924906
void mjCTexture::LoadPNG(mjResource* resource,
4893-
std::vector<unsigned char>& image,
4907+
std::vector<std::byte>& image,
48944908
unsigned int& w, unsigned int& h, bool& is_srgb) {
48954909
LodePNGColorType color_type;
48964910
if (nchannel == 4) {
@@ -4907,13 +4921,14 @@ void mjCTexture::LoadPNG(mjResource* resource,
49074921
w = png_image.Width();
49084922
h = png_image.Height();
49094923
is_srgb = png_image.IsSRGB();
4910-
image = png_image.MoveData();
4924+
4925+
// Move data into image.
4926+
image = std::move(png_image).MoveData();
49114927
}
49124928

49134929
// load KTX file
4914-
void mjCTexture::LoadKTX(mjResource* resource,
4915-
std::vector<unsigned char>& image, unsigned int& w,
4916-
unsigned int& h, bool& is_srgb) {
4930+
void mjCTexture::LoadKTX(mjResource* resource, std::vector<std::byte>& image,
4931+
unsigned int& w, unsigned int& h, bool& is_srgb) {
49174932
const void* buffer = 0;
49184933
int buffer_sz = mju_readResource(resource, &buffer);
49194934

@@ -4933,8 +4948,7 @@ void mjCTexture::LoadKTX(mjResource* resource,
49334948
}
49344949

49354950
// load custom file
4936-
void mjCTexture::LoadCustom(mjResource* resource,
4937-
std::vector<unsigned char>& image,
4951+
void mjCTexture::LoadCustom(mjResource* resource, std::vector<std::byte>& image,
49384952
unsigned int& w, unsigned int& h, bool& is_srgb) {
49394953
const void* buffer = 0;
49404954
int buffer_sz = mju_readResource(resource, &buffer);
@@ -4972,11 +4986,44 @@ void mjCTexture::LoadCustom(mjResource* resource,
49724986
memcpy(image.data(), (void*)(pint+2), w*h*3*sizeof(char));
49734987
}
49744988

4989+
void mjCTexture::FlipIfNeeded(std::vector<std::byte>& image, unsigned int w,
4990+
unsigned int h) {
4991+
// horizontal flip
4992+
if (hflip) {
4993+
for (int r = 0; r < h; r++) {
4994+
for (int c = 0; c < w / 2; c++) {
4995+
int c1 = w - 1 - c;
4996+
auto val1 = nchannel * (r * w + c);
4997+
auto val2 = nchannel * (r * w + c1);
4998+
for (int ch = 0; ch < nchannel; ch++) {
4999+
auto tmp = image[val1 + ch];
5000+
image[val1 + ch] = image[val2 + ch];
5001+
image[val2 + ch] = tmp;
5002+
}
5003+
}
5004+
}
5005+
}
49755006

5007+
// vertical flip
5008+
if (vflip) {
5009+
for (int r = 0; r < h / 2; r++) {
5010+
for (int c = 0; c < w; c++) {
5011+
int r1 = h - 1 - r;
5012+
auto val1 = nchannel * (r * w + c);
5013+
auto val2 = nchannel * (r1 * w + c);
5014+
for (int ch = 0; ch < nchannel; ch++) {
5015+
auto tmp = image[val1 + ch];
5016+
image[val1 + ch] = image[val2 + ch];
5017+
image[val2 + ch] = tmp;
5018+
}
5019+
}
5020+
}
5021+
}
5022+
}
49765023

49775024
// load from PNG or custom file, flip if specified
49785025
void mjCTexture::LoadFlip(std::string filename, const mjVFS* vfs,
4979-
std::vector<unsigned char>& image,
5026+
std::vector<std::byte>& image,
49805027
unsigned int& w, unsigned int& h, bool& is_srgb) {
49815028
std::string asset_type = GetAssetContentType(filename, content_type_);
49825029

@@ -5008,93 +5055,25 @@ void mjCTexture::LoadFlip(std::string filename, const mjVFS* vfs,
50085055
throw err;
50095056
}
50105057

5011-
// horizontal flip
5012-
if (hflip) {
5013-
if (nchannel != 3) {
5014-
throw mjCError(
5015-
this, "currently only 3-channel textures support horizontal flip");
5016-
}
5017-
for (int r=0; r < h; r++) {
5018-
for (int c=0; c < w/2; c++) {
5019-
int c1 = w-1-c;
5020-
unsigned char tmp[3] = {
5021-
image[3*(r*w+c)],
5022-
image[3*(r*w+c)+1],
5023-
image[3*(r*w+c)+2]
5024-
};
5025-
5026-
image[3*(r*w+c)] = image[3*(r*w+c1)];
5027-
image[3*(r*w+c)+1] = image[3*(r*w+c1)+1];
5028-
image[3*(r*w+c)+2] = image[3*(r*w+c1)+2];
5029-
5030-
image[3*(r*w+c1)] = tmp[0];
5031-
image[3*(r*w+c1)+1] = tmp[1];
5032-
image[3*(r*w+c1)+2] = tmp[2];
5033-
}
5034-
}
5035-
}
5036-
5037-
// vertical flip
5038-
if (vflip) {
5039-
if (nchannel != 3) {
5040-
throw mjCError(
5041-
this, "currently only 3-channel textures support vertical flip");
5042-
}
5043-
for (int r=0; r < h/2; r++) {
5044-
for (int c=0; c < w; c++) {
5045-
int r1 = h-1-r;
5046-
unsigned char tmp[3] = {
5047-
image[3*(r*w+c)],
5048-
image[3*(r*w+c)+1],
5049-
image[3*(r*w+c)+2]
5050-
};
5051-
5052-
image[3*(r*w+c)] = image[3*(r1*w+c)];
5053-
image[3*(r*w+c)+1] = image[3*(r1*w+c)+1];
5054-
image[3*(r*w+c)+2] = image[3*(r1*w+c)+2];
5055-
5056-
image[3*(r1*w+c)] = tmp[0];
5057-
image[3*(r1*w+c)+1] = tmp[1];
5058-
image[3*(r1*w+c)+2] = tmp[2];
5059-
}
5060-
}
5061-
}
5058+
FlipIfNeeded(image, w, h);
50625059
}
50635060

5064-
5065-
50665061
// load 2D
50675062
void mjCTexture::Load2D(std::string filename, const mjVFS* vfs) {
50685063
// load PNG or custom
50695064
unsigned int w, h;
50705065
bool is_srgb;
5071-
std::vector<unsigned char> image;
5072-
LoadFlip(filename, vfs, image, w, h, is_srgb);
5066+
5067+
LoadFlip(filename, vfs, data_, w, h, is_srgb);
50735068

50745069
// assign size
50755070
width = w;
50765071
height = h;
50775072
if (colorspace == mjCOLORSPACE_AUTO) {
50785073
colorspace = is_srgb ? mjCOLORSPACE_SRGB : mjCOLORSPACE_LINEAR;
50795074
}
5080-
5081-
// allocate and copy data
5082-
std::int64_t size = static_cast<std::int64_t>(width)*height;
5083-
if (size >= std::numeric_limits<int>::max() / nchannel || size <= 0) {
5084-
throw mjCError(this, "Texture too large");
5085-
}
5086-
try {
5087-
data_.assign(nchannel*size, std::byte(0));
5088-
} catch (const std::bad_alloc& e) {
5089-
throw mjCError(this, "Could not allocate memory for texture '%s' (id %d)",
5090-
(const char*)file_.c_str(), id);
5091-
}
5092-
memcpy(data_.data(), image.data(), nchannel*size);
5093-
image.clear();
50945075
}
50955076

5096-
5097-
50985077
// load cube or skybox from single file (repeated or grid)
50995078
void mjCTexture::LoadCubeSingle(std::string filename, const mjVFS* vfs) {
51005079
// check gridsize
@@ -5105,7 +5084,7 @@ void mjCTexture::LoadCubeSingle(std::string filename, const mjVFS* vfs) {
51055084
// load PNG or custom
51065085
unsigned int w, h;
51075086
bool is_srgb;
5108-
std::vector<unsigned char> image;
5087+
std::vector<std::byte> image;
51095088
LoadFlip(filename, vfs, image, w, h, is_srgb);
51105089

51115090
if (colorspace == mjCOLORSPACE_AUTO) {
@@ -5226,7 +5205,7 @@ void mjCTexture::LoadCubeSeparate(const mjVFS* vfs) {
52265205
// load PNG or custom
52275206
unsigned int w, h;
52285207
bool is_srgb;
5229-
std::vector<unsigned char> image;
5208+
std::vector<std::byte> image;
52305209
LoadFlip(filename.Str(), vfs, image, w, h, is_srgb);
52315210

52325211
// assume all faces have the same colorspace
@@ -5308,6 +5287,9 @@ void mjCTexture::Compile(const mjVFS* vfs) {
53085287
throw mjCError(this, "Texture buffer has incorrect size, given %d expected %d", nullptr,
53095288
data_.size(), nchannel * width * height);
53105289
}
5290+
5291+
// Flip if specified.
5292+
FlipIfNeeded(data_, width, height);
53115293
return;
53125294
}
53135295

src/user/user_objects.h

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1450,18 +1450,17 @@ class mjCTexture : public mjCTexture_, private mjsTexture {
14501450
void LoadCubeSingle(std::string filename, const mjVFS* vfs); // load cube from single file
14511451
void LoadCubeSeparate(const mjVFS* vfs); // load cube from separate files
14521452

1453-
void LoadFlip(std::string filename, const mjVFS* vfs, // load and flip
1454-
std::vector<unsigned char>& image,
1455-
unsigned int& w, unsigned int& h, bool& is_srgb);
1453+
void FlipIfNeeded(std::vector<std::byte>& image, unsigned int w, unsigned int h);
14561454

1457-
void LoadPNG(mjResource* resource,
1458-
std::vector<unsigned char>& image,
1455+
void LoadFlip(std::string filename, const mjVFS* vfs, // load and flip
1456+
std::vector<std::byte>& image, unsigned int& w, unsigned int& h,
1457+
bool& is_srgb);
1458+
1459+
void LoadPNG(mjResource* resource, std::vector<std::byte>& image,
14591460
unsigned int& w, unsigned int& h, bool& is_srgb);
1460-
void LoadKTX(mjResource* resource,
1461-
std::vector<unsigned char>& image,
1461+
void LoadKTX(mjResource* resource, std::vector<std::byte>& image,
14621462
unsigned int& w, unsigned int& h, bool& is_srgb);
1463-
void LoadCustom(mjResource* resource,
1464-
std::vector<unsigned char>& image,
1463+
void LoadCustom(mjResource* resource, std::vector<std::byte>& image,
14651464
unsigned int& w, unsigned int& h, bool& is_srgb);
14661465

14671466
bool clear_data_; // if true, data_ is empty and should be filled by Compile

test/user/user_api_test.cc

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,59 @@ TEST_F(PluginTest, TextureFromBuffer) {
794794
mj_deleteSpec(spec);
795795
}
796796

797+
TEST_F(MujocoTest, TestTextureFlip) {
798+
mjSpec* spec = mj_makeSpec();
799+
EXPECT_THAT(spec, NotNull());
800+
801+
std::vector<std::byte> texture_data = {std::byte{1}, std::byte{2},
802+
std::byte{3}, std::byte{4}};
803+
mjsTexture* texture = mjs_addTexture(spec);
804+
mjs_setName(texture->element, "hflipped");
805+
texture->type = mjTEXTURE_2D;
806+
texture->width = 2;
807+
texture->height = 2;
808+
texture->nchannel = 1;
809+
texture->hflip = true;
810+
mjs_setBuffer(texture->data, texture_data.data(), texture_data.size());
811+
812+
texture_data = {std::byte{11}, std::byte{12}, std::byte{13}, std::byte{21},
813+
std::byte{22}, std::byte{23}, std::byte{31}, std::byte{32},
814+
std::byte{33}, std::byte{41}, std::byte{42}, std::byte{43}};
815+
816+
texture = mjs_addTexture(spec);
817+
mjs_setName(texture->element, "vflipped");
818+
texture->type = mjTEXTURE_2D;
819+
texture->width = 2;
820+
texture->height = 2;
821+
texture->nchannel = 3;
822+
texture->vflip = true;
823+
mjs_setBuffer(texture->data, texture_data.data(), texture_data.size());
824+
825+
mjModel* model = mj_compile(spec, 0);
826+
EXPECT_THAT(model, NotNull()) << mjs_getError(spec);
827+
828+
EXPECT_THAT(model->tex_data[0], 2);
829+
EXPECT_THAT(model->tex_data[1], 1);
830+
EXPECT_THAT(model->tex_data[2], 4);
831+
EXPECT_THAT(model->tex_data[3], 3);
832+
833+
EXPECT_THAT(model->tex_data[4], 31);
834+
EXPECT_THAT(model->tex_data[5], 32);
835+
EXPECT_THAT(model->tex_data[6], 33);
836+
EXPECT_THAT(model->tex_data[7], 41);
837+
EXPECT_THAT(model->tex_data[8], 42);
838+
EXPECT_THAT(model->tex_data[9], 43);
839+
EXPECT_THAT(model->tex_data[10], 11);
840+
EXPECT_THAT(model->tex_data[11], 12);
841+
EXPECT_THAT(model->tex_data[12], 13);
842+
EXPECT_THAT(model->tex_data[13], 21);
843+
EXPECT_THAT(model->tex_data[14], 22);
844+
EXPECT_THAT(model->tex_data[15], 23);
845+
846+
mj_deleteModel(model);
847+
mj_deleteSpec(spec);
848+
}
849+
797850
// -------------------------------- test attach --------------------------------
798851

799852
static constexpr char xml_child[] = R"(

0 commit comments

Comments
 (0)