Skip to content

Instantly share code, notes, and snippets.

@Ohmnivore
Last active September 20, 2018 03:45
Show Gist options
  • Save Ohmnivore/1d2c497664869e2f8e94f5fa77e4d4a3 to your computer and use it in GitHub Desktop.
Save Ohmnivore/1d2c497664869e2f8e94f5fa77e4d4a3 to your computer and use it in GitHub Desktop.
stb_image.h (.png, .jpg, etc) loader for oryol
#define STB_IMAGE_IMPLEMENTATION
#define STBI_ONLY_PNG
#define STBI_NO_STDIO
#define STBI_NO_FAILURE_STRINGS
#include "stb/stb_image.h"
#include "IO/IO.h"
#include "Gfx/Gfx.h"
#include "Gfx/private/gfxResourceContainer.h"
#include "STBTextureLoader.h"
namespace Oryol {
//------------------------------------------------------------------------------
STBTextureLoader::STBTextureLoader(const TextureSetup& setup_) :
TextureLoaderBase(setup_) {
// empty
}
//------------------------------------------------------------------------------
STBTextureLoader::STBTextureLoader(const TextureSetup& setup_, LoadedFunc loadedFunc_) :
TextureLoaderBase(setup_, loadedFunc_) {
// empty
}
//------------------------------------------------------------------------------
STBTextureLoader::~STBTextureLoader() {
o_assert_dbg(!this->ioRequest);
}
//------------------------------------------------------------------------------
void
STBTextureLoader::Cancel() {
if (this->ioRequest) {
this->ioRequest->Cancelled = true;
this->ioRequest = nullptr;
}
}
//------------------------------------------------------------------------------
Id
STBTextureLoader::Start() {
this->resId = Gfx::resource()->prepareAsync(this->setup);
this->ioRequest = IO::LoadFile(setup.Locator.Location());
return this->resId;
}
//------------------------------------------------------------------------------
ResourceState::Code
STBTextureLoader::Continue() {
o_assert_dbg(this->resId.IsValid());
o_assert_dbg(this->ioRequest.isValid());
ResourceState::Code result = ResourceState::Pending;
if (this->ioRequest->Handled) {
if (IOStatus::OK == this->ioRequest->Status) {
// yeah, IO is done, let stb_image parse the texture data
// and create the texture resource
const uint8_t* data = this->ioRequest->Data.Data();
const int numBytes = this->ioRequest->Data.Size();
int width, height, numComponents;
unsigned char* imgData = stbi_load_from_memory(data, numBytes, &width, &height, &numComponents, 0);
if (imgData != NULL) {
const int size = width * height * numComponents;
TextureSetup texSetup = this->buildSetup(this->setup, width, height, numComponents, imgData, size);
// call the Loaded callback if defined, this
// gives the app a chance to look at the
// setup object, and possibly modify it
if (this->onLoaded) {
this->onLoaded(texSetup);
}
// NOTE: the prepared texture resource might have already been
// destroyed at this point, if this happens, initAsync will
// silently fail and return ResourceState::InvalidState
// (the same for failedAsync)
result = Gfx::resource()->initAsync(this->resId, texSetup, imgData, size);
// Free data!
stbi_image_free(imgData);
}
else {
result = Gfx::resource()->failedAsync(this->resId);
}
}
else {
// IO had failed
result = Gfx::resource()->failedAsync(this->resId);
}
this->ioRequest = nullptr;
}
return result;
}
//------------------------------------------------------------------------------
TextureSetup
STBTextureLoader::buildSetup(const TextureSetup& blueprint,
const int width, const int height, const int numComponents,
const uint8_t* data, const int size)
{
PixelFormat::Code pixelFormat = PixelFormat::InvalidPixelFormat;
if (numComponents == 3)
pixelFormat = PixelFormat::RGB8;
else if (numComponents == 4)
pixelFormat = PixelFormat::RGBA8;
o_assert(PixelFormat::InvalidPixelFormat != pixelFormat);
TextureSetup newSetup;
newSetup = TextureSetup::FromPixelData2D(width, height, 1, pixelFormat, this->setup);
// setup mipmap offsets
newSetup.ImageData.Offsets[0][0] = 0;
newSetup.ImageData.Sizes[0][0] = size;
return newSetup;
}
} // namespace Oryol
#pragma once
#include "Gfx/TextureLoaderBase.h"
#include "IO/private/ioRequests.h"
namespace Oryol {
class STBTextureLoader : public TextureLoaderBase {
OryolClassDecl(STBTextureLoader);
public:
/// constructor without success-callback
STBTextureLoader(const TextureSetup& setup);
/// constructor with success callback
STBTextureLoader(const TextureSetup& setup, LoadedFunc onLoaded);
/// destructor
~STBTextureLoader();
/// start loading, return a resource id
virtual Id Start() override;
/// continue loading, return resource state (Pending, Valid, Failed)
virtual ResourceState::Code Continue() override;
/// cancel the load process
virtual void Cancel() override;
private:
/// convert image context attrs into a TextureSetup object
TextureSetup buildSetup(const TextureSetup& blueprint,
const int width, const int height, const int numComponents,
const uint8_t* data, const int size);
Id resId;
Ptr<IORead> ioRequest;
};
} // namespace Oryol
@Ohmnivore
Copy link
Author

Make sure to setup your own #define statements and include path in STBTextureLoader.cc according to your needs. The docs for the #defines can be found in stb_image.h.

@Ohmnivore
Copy link
Author

Also, only supports 2D textures with no mipmaps. (No texture arrays either)

Supports only RGB and RGBA channel configuration.

@Ohmnivore
Copy link
Author

Note: I had forgotten to call stbi_image_free(imgData);. This created a memory leak :(

This is now fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment