Skip to content

Commit c736fab

Browse files
committed
common: Rewrote image conversion, other misc improovements/refactors
1 parent 8d02e5a commit c736fab

File tree

9 files changed

+428
-107
lines changed

9 files changed

+428
-107
lines changed

CMakeLists.txt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ set(CMAKE_CXX_STANDARD 20)
77

88
# Project settings
99
option(BUILD_GUI "Build the VTFViewer GUI" ON)
10+
option(BUILD_TESTS "Build test binaries" ON)
1011

1112
# Global flags, mainly for UNIX. Use $ORIGIN rpath & -fPIC
1213
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
@@ -18,6 +19,7 @@ set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
1819
# Build vtflib as static lib
1920
add_subdirectory(external/vtflib)
2021
add_subdirectory(external/fmtlib)
22+
add_subdirectory(external/gtest)
2123

2224
add_definitions(-DVTFLIB_STATIC=1)
2325

@@ -99,6 +101,32 @@ if (BUILD_GUI)
99101
)
100102
endif()
101103

104+
##############################
105+
# Tests
106+
##############################
107+
108+
if (BUILD_TESTS)
109+
add_executable(
110+
tests
111+
112+
src/tests/image_tests.cpp
113+
)
114+
115+
target_link_libraries(
116+
tests PRIVATE
117+
118+
gtest
119+
vtflib_static
120+
com
121+
)
122+
123+
target_include_directories(
124+
tests PRIVATE
125+
126+
src
127+
)
128+
endif()
129+
102130
##############################
103131
# Version header
104132
##############################

src/cli/action_convert.cpp

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -319,15 +319,15 @@ bool ActionConvert::process_file(
319319
std::max(formatInfo.uiRedBitsPerPixel, formatInfo.uiGreenBitsPerPixel),
320320
std::max(formatInfo.uiBlueBitsPerPixel, formatInfo.uiAlphaBitsPerPixel));
321321
if (maxBpp > 16) {
322-
procChanType = imglib::Float;
322+
procChanType = imglib::ChannelType::Float;
323323
return IMAGE_FORMAT_RGBA32323232F;
324324
}
325325
else if (maxBpp > 8) {
326-
procChanType = imglib::UInt16;
327-
return IMAGE_FORMAT_RGBA16161616F;
326+
procChanType = imglib::ChannelType::UInt16;
327+
return IMAGE_FORMAT_RGBA16161616;
328328
}
329329
else {
330-
procChanType = imglib::UInt8;
330+
procChanType = imglib::ChannelType::UInt8;
331331
return IMAGE_FORMAT_RGBA8888;
332332
}
333333
}();
@@ -521,6 +521,14 @@ bool ActionConvert::add_image_data(
521521
return false;
522522
}
523523

524+
// Hack for VTFLib; Ensure we have an alpha channel because that's well supported in that horrible code
525+
if (image->channels() < 4 && image->type() != imglib::ChannelType::UInt8) {
526+
if (!image->convert(image->type(), 4)) {
527+
std::cerr << fmt::format("Failed to convert {}\n", imageSrc.c_str());
528+
return false;
529+
}
530+
}
531+
524532
// Add the raw image data
525533
return add_image_data_raw(
526534
file, image->data(), format, image->vtf_format(), image->width(), image->height(), create);

src/cli/action_extract.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ bool ActionExtract::extract_file(
217217
return false;
218218
}
219219

220-
imglib::Image image(imageData, destIsFloat ? imglib::Float : imglib::UInt8, comps, w, h, true);
220+
imglib::Image image(imageData, destIsFloat ? imglib::ChannelType::Float : imglib::ChannelType::UInt8, comps, w, h, true);
221221
if (!image.save(outFile.string().c_str(), targetFmt)) {
222222
std::cerr << fmt::format("Could not save image to '{}'!\n", outFile.string());
223223
return false;

src/cli/action_pack.cpp

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ int ActionPack::exec(const OptionList& opts) {
212212
//
213213
static std::shared_ptr<imglib::Image> load_image(const std::filesystem::path& path) {
214214

215-
auto img = imglib::Image::load(path);
215+
auto img = imglib::Image::load(path); // Force UInt8 for packed images
216216
if (!img)
217217
std::cerr << fmt::format("Could not load image '{}'\n", path.string());
218218
return img;
@@ -236,20 +236,23 @@ static void determine_size(int* w, int* h, const std::shared_ptr<imglib::Image>
236236
//
237237
// Resize images if required and converts too!
238238
//
239-
static void resize_if_required(const std::shared_ptr<imglib::Image>& image, int w, int h) {
239+
static bool resize_if_required(const std::shared_ptr<imglib::Image>& image, int w, int h) {
240240
if (!image)
241-
return;
241+
return true;
242242

243243
// @TODO: For now we're just going to force 8 bit per channel.
244244
// Sometimes we do get 16bpc images, mainly for height data, but we're cramming that into a RGBA8888 texture
245245
// anyways. It'd be best to eventually support RGBA16F normals for instances where you need precise height data.
246-
if (image->type() != imglib::UInt8) {
247-
assert(image->convert(imglib::UInt8));
246+
if (image->type() != imglib::ChannelType::UInt8) {
247+
if (!image->convert(imglib::ChannelType::UInt8)) {
248+
std::cerr << "Failed to convert image\n";
249+
return false;
250+
}
248251
}
249252

250253
if (image->width() == w && image->height() == h)
251-
return;
252-
assert(image->resize(w, h));
254+
return true;
255+
return image->resize(w, h);
253256
}
254257

255258
//
@@ -296,10 +299,14 @@ bool ActionPack::pack_mrao(
296299
}
297300

298301
// Resize images if required
299-
resize_if_required(roughnessData, w, h);
300-
resize_if_required(metalnessData, w, h);
301-
resize_if_required(aoData, w, h);
302-
resize_if_required(tmaskData, w, h);
302+
if (!resize_if_required(roughnessData, w, h))
303+
return false;
304+
if (!resize_if_required(metalnessData, w, h))
305+
return false;
306+
if (!resize_if_required(aoData, w, h))
307+
return false;
308+
if (!resize_if_required(tmaskData, w, h))
309+
return false;
303310

304311
// Packing config
305312
pack::ChannelPack_t pack[] = {
@@ -400,8 +407,10 @@ bool ActionPack::pack_normal(
400407
}
401408

402409
// Resize images if required
403-
resize_if_required(normalData, w, h);
404-
resize_if_required(heightData, w, h);
410+
if (!resize_if_required(normalData, w, h))
411+
return false;
412+
if (!resize_if_required(heightData, w, h))
413+
return false;
405414

406415
// Convert normal to DX if necessary
407416
if (isGL)

src/common/image.cpp

Lines changed: 65 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include "image.hpp"
33
#include "util.hpp"
44
#include "strtools.hpp"
5+
#include "lwiconv.hpp"
56

67
#include <cstring>
78
#include <cassert>
@@ -52,16 +53,16 @@ Image::~Image() {
5253
free(m_data);
5354
}
5455

55-
std::shared_ptr<Image> Image::load(const char* path) {
56+
std::shared_ptr<Image> Image::load(const char* path, ChannelType convertOnLoad) {
5657
FILE* fp = fopen(path, "rb");
5758
if (!fp)
5859
return nullptr;
59-
auto img = load(fp);
60+
auto img = load(fp, convertOnLoad);
6061
fclose(fp);
6162
return img;
6263
}
6364

64-
std::shared_ptr<Image> Image::load(FILE* fp) {
65+
std::shared_ptr<Image> Image::load(FILE* fp, ChannelType convertOnLoad) {
6566
auto info = image_info(fp);
6667
auto image = std::make_shared<Image>();
6768
if (info.type == ChannelType::Float) {
@@ -73,9 +74,15 @@ std::shared_ptr<Image> Image::load(FILE* fp) {
7374
else {
7475
image->m_data = stbi_load_from_file(fp, &image->m_width, &image->m_height, &image->m_comps, info.comps);
7576
}
77+
image->m_type = info.type;
7678

7779
if (!image->m_data)
7880
return nullptr;
81+
82+
if (convertOnLoad != ChannelType::None && convertOnLoad != info.type)
83+
if (!image->convert(convertOnLoad))
84+
return nullptr; // Convert on load failed
85+
7986
return image;
8087
}
8188

@@ -86,6 +93,8 @@ void Image::clear() {
8693
}
8794

8895
bool Image::save(const char* file, FileFormat format) {
96+
using enum ChannelType;
97+
8998
if (!file || !m_data || (format != Tga && format != Png && format != Jpeg && format != Bmp && format != Hdr))
9099
return false;
91100

@@ -97,7 +106,7 @@ bool Image::save(const char* file, FileFormat format) {
97106
if (m_type != Float) {
98107
dataToUse = malloc(m_width * m_height * sizeof(float) * m_comps);
99108
dataIsOurs = true;
100-
if (!convert_formats(m_data, dataToUse, m_type, Float, m_comps, m_width, m_height)) {
109+
if (!convert_formats(m_data, dataToUse, m_type, Float, m_width, m_height, m_comps, m_comps, pixel_size(), imglib::pixel_size(Float, m_comps))) {
101110
free(dataToUse);
102111
return false;
103112
}
@@ -115,7 +124,7 @@ bool Image::save(const char* file, FileFormat format) {
115124
if (m_type != UInt8) {
116125
dataToUse = malloc(m_width * m_height * sizeof(uint8_t) * m_comps);
117126
dataIsOurs = true;
118-
if (!convert_formats(m_data, dataToUse, m_type, UInt8, m_comps, m_width, m_height)) {
127+
if (!convert_formats(m_data, dataToUse, m_type, UInt8, m_width, m_height, m_comps, m_comps, pixel_size(), imglib::pixel_size(UInt8, m_comps))) {
119128
free(dataToUse);
120129
return false;
121130
}
@@ -157,10 +166,10 @@ bool Image::resize(int newW, int newH) {
157166

158167
VTFImageFormat Image::vtf_format() const {
159168
switch (m_type) {
160-
case imglib::UInt16:
161-
// @TODO: How to handle RGBA16? DONT i guess
162-
return IMAGE_FORMAT_RGBA16161616F;
163-
case imglib::Float:
169+
case ChannelType::UInt16:
170+
// @TODO: How to handle RGB16? DONT i guess
171+
return IMAGE_FORMAT_RGBA16161616;
172+
case ChannelType::Float:
164173
return (m_comps == 3) ? IMAGE_FORMAT_RGB323232F
165174
: (m_comps == 1 ? IMAGE_FORMAT_R32F : IMAGE_FORMAT_RGBA32323232F);
166175
default:
@@ -170,6 +179,7 @@ VTFImageFormat Image::vtf_format() const {
170179

171180
bool imglib::resize(
172181
void* indata, void** useroutdata, ChannelType srcType, int comps, int w, int h, int newW, int newH) {
182+
using enum ChannelType;
173183
stbir_datatype type;
174184
switch (srcType) {
175185
case Float:
@@ -215,63 +225,66 @@ size_t imglib::bytes_for_image(int w, int h, ChannelType type, int comps) {
215225
return w * h * comps * bpc;
216226
}
217227

218-
template <int COMPS>
219228
bool convert_formats_internal(
220-
const void* srcData, void* dstData, ChannelType srcChanType, ChannelType dstChanType, int w, int h) {
221-
static_assert(COMPS > 0 && COMPS <= 4, "Comps is out of range");
229+
const void* srcData, void* dstData, ChannelType srcChanType, ChannelType dstChanType, int w, int h, int inComps, int outComps, int inStride, int outStride, const lwiconv::PixelF& pdef) {
230+
using enum ChannelType;
222231

223232
if (srcChanType == UInt8) {
224233
// RGBX32
225234
if (dstChanType == Float)
226-
convert_8_to_32<COMPS>(srcData, dstData, w, h);
235+
lwiconv::convert_generic<uint8_t, float>(srcData, dstData, w, h, inComps, outComps, inStride, outStride, pdef);
227236
// RGBX16
228237
else if (dstChanType == UInt16)
229-
convert_8_to_16<COMPS>(srcData, dstData, w, h);
238+
lwiconv::convert_generic<uint8_t, uint16_t>(srcData, dstData, w, h, inComps, outComps, inStride, outStride, pdef);
239+
// RGBX8 (just adding/removing channel(s))
240+
else if (dstChanType == UInt8)
241+
lwiconv::convert_generic<uint8_t, uint8_t>(srcData, dstData, w, h, inComps, outComps, inStride, outStride, pdef);
230242
return true;
231243
}
232244
// RGBX32 -> RGBX[8|16]
233245
else if (srcChanType == Float) {
234246
// RGBX16
235247
if (dstChanType == UInt16)
236-
convert_32_to_16<COMPS>(srcData, dstData, w, h);
248+
lwiconv::convert_generic<float, uint16_t>(srcData, dstData, w, h, inComps, outComps, inStride, outStride, pdef);
237249
// RGBX8
238250
else if (dstChanType == UInt8)
239-
convert_32_to_8<COMPS>(srcData, dstData, w, h);
251+
lwiconv::convert_generic<float, uint8_t>(srcData, dstData, w, h, inComps, outComps, inStride, outStride, pdef);
252+
// RGBX32 (just adding/removing channel(s))
253+
else if (dstChanType == Float)
254+
lwiconv::convert_generic<float, float>(srcData, dstData, w, h, inComps, outComps, inStride, outStride, pdef);
240255
return true;
241256
}
242-
// RGBX16
257+
// RGBX16 -> RGBX[8|32F]
243258
else if (srcChanType == UInt16) {
259+
// RGBX8
244260
if (dstChanType == UInt8)
245-
convert_16_to_8<COMPS>(srcData, dstData, w, h);
261+
lwiconv::convert_generic<uint16_t, uint8_t>(srcData, dstData, w, h, inComps, outComps, inStride, outStride, pdef);
262+
// RGBX32
246263
else if (dstChanType == Float)
247-
convert_16_to_32<COMPS>(srcData, dstData, w, h);
264+
lwiconv::convert_generic<uint16_t, float>(srcData, dstData, w, h, inComps, outComps, inStride, outStride, pdef);
265+
// RGBX16 (just adding/removing channel(s))
266+
else if (dstChanType == UInt16)
267+
lwiconv::convert_generic<uint16_t, uint16_t>(srcData, dstData, w, h, inComps, outComps, inStride, outStride, pdef);
248268
return true;
249269
}
250270

251271
return false;
252272
}
253273

254274
bool imglib::convert_formats(
255-
const void* srcData, void* dstData, ChannelType srcChanType, ChannelType dstChanType, int comps, int w, int h) {
275+
const void* srcData, void* dstData, ChannelType srcChanType, ChannelType dstChanType, int w, int h, int inComps, int outComps, int inStride, int outStride, const lwiconv::PixelF& pdef) {
256276
// No conv needed
257-
if (srcChanType == dstChanType)
277+
if (srcChanType == dstChanType && inComps == outComps)
258278
return true;
259279

260-
if (comps == 4)
261-
return convert_formats_internal<4>(srcData, dstData, srcChanType, dstChanType, w, h);
262-
else if (comps == 3)
263-
return convert_formats_internal<3>(srcData, dstData, srcChanType, dstChanType, w, h);
264-
else if (comps == 2)
265-
return convert_formats_internal<2>(srcData, dstData, srcChanType, dstChanType, w, h);
266-
else if (comps == 1)
267-
return convert_formats_internal<1>(srcData, dstData, srcChanType, dstChanType, w, h);
268-
return false;
280+
return convert_formats_internal(srcData, dstData, srcChanType, dstChanType, w, h, inComps, outComps, inStride, outStride, pdef);
269281
}
270282

271-
bool Image::convert(ChannelType dstChanType) {
272-
void* dst = malloc(imglib::bytes_for_image(m_width, m_height, m_type, m_comps));
283+
bool Image::convert(ChannelType dstChanType, int channels, const lwiconv::PixelF& pdef) {
284+
channels = channels <= 0 ? m_comps : channels;
285+
void* dst = malloc(imglib::bytes_for_image(m_width, m_height, m_type, channels));
273286

274-
if (!convert_formats(m_data, dst, m_type, dstChanType, m_comps, m_width, m_height)) {
287+
if (!convert_formats(m_data, dst, m_type, dstChanType, m_width, m_height, m_comps, channels, pixel_size(), channels * channel_size(dstChanType), pdef)) {
275288
free(dst);
276289
return false;
277290
}
@@ -302,6 +315,7 @@ static bool process_image_internal(void* indata, int comps, int w, int h, ProcFl
302315
}
303316

304317
bool Image::process(ProcFlags flags) {
318+
using enum ChannelType;
305319
switch (m_type) {
306320
case UInt8:
307321
return process_image_internal<uint8_t>(m_data, m_comps, m_width, m_height, flags);
@@ -374,3 +388,21 @@ static ImageInfo_t image_info(FILE* fp) {
374388

375389
return info;
376390
}
391+
392+
size_t imglib::pixel_size(ChannelType type, int channels) {
393+
return channels * channel_size(type);
394+
}
395+
396+
size_t imglib::channel_size(ChannelType type) {
397+
switch(type) {
398+
case imglib::ChannelType::UInt8:
399+
return 1;
400+
case imglib::ChannelType::UInt16:
401+
return 2;
402+
case imglib::ChannelType::Float:
403+
return 4;
404+
default:
405+
assert(0);
406+
return 1;
407+
}
408+
}

0 commit comments

Comments
 (0)