535 lines
17 KiB
C++
535 lines
17 KiB
C++
|
|
#include "../ImNodeFlow.h"
|
|
#include <cstdlib>
|
|
#include <iterator>
|
|
#include <ostream>
|
|
|
|
#if defined(__APPLE__)
|
|
#include <SDL2/SDL.h>
|
|
#if defined(IMGUI_IMPL_OPENGL_ES2)
|
|
#include <SDL2/SDL_opengles.h>
|
|
#else
|
|
#include <SDL2/SDL_opengl.h>
|
|
#endif
|
|
#else
|
|
#include <SDL.h>
|
|
#if defined(IMGUI_IMPL_OPENGL_ES2)
|
|
#include <SDL_opengles2.h>
|
|
#else
|
|
#include <SDL_opengl.h>
|
|
#endif
|
|
#endif
|
|
|
|
#include "../image_model.h"
|
|
#include "../imfilebrowser.h"
|
|
#include "../stb_image.h"
|
|
#include "../uuid.h"
|
|
#include <cstdint>
|
|
#include <iostream>
|
|
|
|
#ifdef __EMSCRIPTEN__
|
|
#include "../emscripten_browser_file.h"
|
|
#endif
|
|
|
|
struct Texture {
|
|
ImTextureID texture;
|
|
ImVec2 size;
|
|
};
|
|
|
|
struct LoadCallback {
|
|
std::string filename;
|
|
std::string mime_type;
|
|
std::string_view buffer;
|
|
};
|
|
|
|
class Exposure : public ImFlow::BaseNode {
|
|
public:
|
|
ImageBundle returnBundle;
|
|
Exposure() {
|
|
setTitle("Exposure");
|
|
setStyle(ImFlow::NodeStyle::green());
|
|
addIN<ImageBundle>("Image", ImageBundle());
|
|
addOUT<ImageBundle>("Image Out")->behaviour([this]() {
|
|
return returnBundle;
|
|
});
|
|
}
|
|
|
|
~Exposure() { stbi_image_free(returnBundle.image); }
|
|
|
|
void draw() override {
|
|
ImGui::SetNextItemWidth(100);
|
|
ImGui::SliderInt("", &brightnessVal, -255, 255);
|
|
ImageBundle val = getInVal<ImageBundle>("Image");
|
|
|
|
// If the input is valid, and out buffers are not
|
|
if (val.loaded && !returnBundle.loaded) {
|
|
int img_size = val.width * val.height * val.channels;
|
|
uint8_t *duplicated_img = (uint8_t *)malloc(img_size);
|
|
memcpy(duplicated_img, val.image, img_size);
|
|
returnBundle.image = duplicated_img;
|
|
returnBundle.height = val.height;
|
|
returnBundle.width = val.width;
|
|
returnBundle.channels = val.channels;
|
|
returnBundle.loaded = true;
|
|
returnBundle.fname = uuid::generate_uuid_v4();
|
|
fname = val.fname;
|
|
compute(val.image, returnBundle.image, img_size, returnBundle.channels);
|
|
}
|
|
|
|
// If the input is invalid, and are buffers are valid
|
|
if (!val.loaded && returnBundle.loaded) {
|
|
stbi_image_free(returnBundle.image);
|
|
returnBundle.loaded = false;
|
|
}
|
|
|
|
// If the input has changed
|
|
if (val.fname != fname) {
|
|
int img_size = val.width * val.height * val.channels;
|
|
int old_img_size =
|
|
returnBundle.width * returnBundle.height * returnBundle.channels;
|
|
if (img_size == old_img_size) {
|
|
memcpy(returnBundle.image, val.image, img_size);
|
|
returnBundle.fname = uuid::generate_uuid_v4();
|
|
fname = val.fname;
|
|
int img_size =
|
|
returnBundle.width * returnBundle.height * returnBundle.channels;
|
|
compute(val.image, returnBundle.image, img_size, returnBundle.channels);
|
|
} else {
|
|
stbi_image_free(returnBundle.image);
|
|
uint8_t *duplicated_img = (uint8_t *)malloc(img_size);
|
|
memcpy(duplicated_img, val.image, img_size);
|
|
returnBundle.image = duplicated_img;
|
|
returnBundle.height = val.height;
|
|
returnBundle.width = val.width;
|
|
returnBundle.channels = val.channels;
|
|
returnBundle.fname = uuid::generate_uuid_v4();
|
|
fname = val.fname;
|
|
int img_size =
|
|
returnBundle.width * returnBundle.height * returnBundle.channels;
|
|
compute(val.image, returnBundle.image, img_size, returnBundle.channels);
|
|
}
|
|
}
|
|
|
|
if (returnBundle.loaded) {
|
|
if (brightnessVal != prev_brightness) {
|
|
int img_size =
|
|
returnBundle.width * returnBundle.height * returnBundle.channels;
|
|
compute(val.image, returnBundle.image, img_size, returnBundle.channels);
|
|
prev_brightness = brightnessVal;
|
|
returnBundle.fname = uuid::generate_uuid_v4();
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
static int formula(uint8_t color, int val) {
|
|
float vf = static_cast<float>(val) / 255.0;
|
|
vf = vf + 1.0;
|
|
float cf = static_cast<float>(color) / 255.0;
|
|
float ret_val = (cf * vf) * 255.0f;
|
|
return static_cast<int>(ret_val);
|
|
}
|
|
|
|
void compute(uint8_t *input, uint8_t *output, int size, int channels) {
|
|
for (int i = 0; i < size; i += channels) {
|
|
uint8_t r = std::clamp(formula(input[i], brightnessVal), 0, 255);
|
|
uint8_t g = std::clamp(formula(input[i + 1], brightnessVal), 0, 255);
|
|
uint8_t b = std::clamp(formula(input[i + 2], brightnessVal), 0, 255);
|
|
output[i] = r;
|
|
output[i + 1] = g;
|
|
output[i + 2] = b;
|
|
}
|
|
}
|
|
|
|
int brightnessVal = 0;
|
|
int prev_brightness = 0;
|
|
std::string fname;
|
|
};
|
|
|
|
class Contrast : public ImFlow::BaseNode {
|
|
public:
|
|
ImageBundle returnBundle;
|
|
Contrast() {
|
|
setTitle("Contrast");
|
|
setStyle(ImFlow::NodeStyle::green());
|
|
addIN<ImageBundle>("Image", ImageBundle());
|
|
addOUT<ImageBundle>("Image Out")->behaviour([this]() {
|
|
return returnBundle;
|
|
});
|
|
}
|
|
|
|
~Contrast() { stbi_image_free(returnBundle.image); }
|
|
|
|
void draw() override {
|
|
ImGui::SetNextItemWidth(100);
|
|
ImGui::SliderInt("", &brightnessVal, -255, 255);
|
|
ImageBundle val = getInVal<ImageBundle>("Image");
|
|
|
|
// If the input is valid, and out buffers are not
|
|
if (val.loaded && !returnBundle.loaded) {
|
|
int img_size = val.width * val.height * val.channels;
|
|
uint8_t *duplicated_img = (uint8_t *)malloc(img_size);
|
|
memcpy(duplicated_img, val.image, img_size);
|
|
returnBundle.image = duplicated_img;
|
|
returnBundle.height = val.height;
|
|
returnBundle.width = val.width;
|
|
returnBundle.channels = val.channels;
|
|
returnBundle.loaded = true;
|
|
returnBundle.fname = uuid::generate_uuid_v4();
|
|
fname = val.fname;
|
|
compute(val.image, returnBundle.image, img_size, returnBundle.channels);
|
|
}
|
|
|
|
// If the input is invalid, and are buffers are valid
|
|
if (!val.loaded && returnBundle.loaded) {
|
|
stbi_image_free(returnBundle.image);
|
|
returnBundle.loaded = false;
|
|
}
|
|
|
|
// If the input has changed
|
|
if (val.fname != fname) {
|
|
int img_size = val.width * val.height * val.channels;
|
|
int old_img_size =
|
|
returnBundle.width * returnBundle.height * returnBundle.channels;
|
|
if (img_size == old_img_size) {
|
|
memcpy(returnBundle.image, val.image, img_size);
|
|
returnBundle.fname = uuid::generate_uuid_v4();
|
|
fname = val.fname;
|
|
int img_size =
|
|
returnBundle.width * returnBundle.height * returnBundle.channels;
|
|
compute(val.image, returnBundle.image, img_size, returnBundle.channels);
|
|
} else {
|
|
stbi_image_free(returnBundle.image);
|
|
uint8_t *duplicated_img = (uint8_t *)malloc(img_size);
|
|
memcpy(duplicated_img, val.image, img_size);
|
|
returnBundle.image = duplicated_img;
|
|
returnBundle.height = val.height;
|
|
returnBundle.width = val.width;
|
|
returnBundle.channels = val.channels;
|
|
returnBundle.fname = uuid::generate_uuid_v4();
|
|
fname = val.fname;
|
|
int img_size =
|
|
returnBundle.width * returnBundle.height * returnBundle.channels;
|
|
compute(val.image, returnBundle.image, img_size, returnBundle.channels);
|
|
}
|
|
}
|
|
|
|
if (returnBundle.loaded) {
|
|
if (brightnessVal != prev_brightness) {
|
|
int img_size =
|
|
returnBundle.width * returnBundle.height * returnBundle.channels;
|
|
compute(val.image, returnBundle.image, img_size, returnBundle.channels);
|
|
prev_brightness = brightnessVal;
|
|
returnBundle.fname = uuid::generate_uuid_v4();
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
static int formula(uint8_t color, int val) {
|
|
float vf = static_cast<float>(val) / 255.0;
|
|
vf = vf + 1.0;
|
|
float cf = static_cast<float>(color) / 255.0;
|
|
float ret_val = (((cf - 0.5f) * vf) + 0.5f) * 255.0f;
|
|
return static_cast<int>(ret_val);
|
|
}
|
|
|
|
void compute(uint8_t *input, uint8_t *output, int size, int channels) {
|
|
int nBval = brightnessVal + 256;
|
|
for (int i = 0; i < size; i += channels) {
|
|
uint8_t r = std::clamp(formula(input[i], brightnessVal), 0, 255);
|
|
uint8_t g = std::clamp(formula(input[i + 1], brightnessVal), 0, 255);
|
|
uint8_t b = std::clamp(formula(input[i + 2], brightnessVal), 0, 255);
|
|
output[i] = r;
|
|
output[i + 1] = g;
|
|
output[i + 2] = b;
|
|
}
|
|
}
|
|
|
|
int brightnessVal = 0;
|
|
int prev_brightness = 0;
|
|
std::string fname;
|
|
};
|
|
|
|
class SimpleBrightness : public ImFlow::BaseNode {
|
|
public:
|
|
ImageBundle returnBundle;
|
|
SimpleBrightness() {
|
|
setTitle("Simple Brightness");
|
|
setStyle(ImFlow::NodeStyle::green());
|
|
addIN<ImageBundle>("Image", ImageBundle());
|
|
addOUT<ImageBundle>("Image Out")->behaviour([this]() {
|
|
return returnBundle;
|
|
});
|
|
}
|
|
|
|
~SimpleBrightness() { stbi_image_free(returnBundle.image); }
|
|
|
|
void draw() override {
|
|
ImGui::SetNextItemWidth(100);
|
|
ImGui::SliderInt("", &brightnessVal, -255, 255);
|
|
ImageBundle val = getInVal<ImageBundle>("Image");
|
|
|
|
// If the input is valid, and out buffers are not
|
|
if (val.loaded && !returnBundle.loaded) {
|
|
int img_size = val.width * val.height * val.channels;
|
|
uint8_t *duplicated_img = (uint8_t *)malloc(img_size);
|
|
memcpy(duplicated_img, val.image, img_size);
|
|
returnBundle.image = duplicated_img;
|
|
returnBundle.height = val.height;
|
|
returnBundle.width = val.width;
|
|
returnBundle.channels = val.channels;
|
|
returnBundle.loaded = true;
|
|
returnBundle.fname = uuid::generate_uuid_v4();
|
|
fname = val.fname;
|
|
compute(val.image, returnBundle.image, img_size, returnBundle.channels);
|
|
}
|
|
|
|
// If the input is invalid, and are buffers are valid
|
|
if (!val.loaded && returnBundle.loaded) {
|
|
stbi_image_free(returnBundle.image);
|
|
returnBundle.loaded = false;
|
|
}
|
|
|
|
// If the input has changed
|
|
if (val.fname != fname) {
|
|
int img_size = val.width * val.height * val.channels;
|
|
int old_img_size =
|
|
returnBundle.width * returnBundle.height * returnBundle.channels;
|
|
if (img_size == old_img_size) {
|
|
memcpy(returnBundle.image, val.image, img_size);
|
|
returnBundle.fname = uuid::generate_uuid_v4();
|
|
fname = val.fname;
|
|
int img_size =
|
|
returnBundle.width * returnBundle.height * returnBundle.channels;
|
|
compute(val.image, returnBundle.image, img_size, returnBundle.channels);
|
|
} else {
|
|
stbi_image_free(returnBundle.image);
|
|
uint8_t *duplicated_img = (uint8_t *)malloc(img_size);
|
|
memcpy(duplicated_img, val.image, img_size);
|
|
returnBundle.image = duplicated_img;
|
|
returnBundle.height = val.height;
|
|
returnBundle.width = val.width;
|
|
returnBundle.channels = val.channels;
|
|
returnBundle.fname = uuid::generate_uuid_v4();
|
|
fname = val.fname;
|
|
int img_size =
|
|
returnBundle.width * returnBundle.height * returnBundle.channels;
|
|
compute(val.image, returnBundle.image, img_size, returnBundle.channels);
|
|
}
|
|
}
|
|
|
|
if (returnBundle.loaded) {
|
|
if (brightnessVal != prev_brightness) {
|
|
int img_size =
|
|
returnBundle.width * returnBundle.height * returnBundle.channels;
|
|
compute(val.image, returnBundle.image, img_size, returnBundle.channels);
|
|
prev_brightness = brightnessVal;
|
|
returnBundle.fname = uuid::generate_uuid_v4();
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
void compute(uint8_t *input, uint8_t *output, int size, int channels) {
|
|
for (int i = 0; i < size; i += channels) {
|
|
uint8_t r = std::clamp(input[i] + brightnessVal, 0, 255);
|
|
uint8_t g = std::clamp(input[i + 1] + brightnessVal, 0, 255);
|
|
uint8_t b = std::clamp(input[i + 2] + brightnessVal, 0, 255);
|
|
output[i] = r;
|
|
output[i + 1] = g;
|
|
output[i + 2] = b;
|
|
}
|
|
}
|
|
|
|
int brightnessVal = 0;
|
|
int prev_brightness = 0;
|
|
std::string fname;
|
|
};
|
|
|
|
class PreviewImage : public ImFlow::BaseNode {
|
|
public:
|
|
PreviewImage() {
|
|
setTitle("Output");
|
|
setStyle(ImFlow::NodeStyle::cyan());
|
|
addIN<ImageBundle>("Image", ImageBundle());
|
|
}
|
|
|
|
~PreviewImage() {
|
|
if (loaded) {
|
|
glDeleteTextures(1, &txHandle);
|
|
}
|
|
}
|
|
|
|
void draw() override {
|
|
ImageBundle val = getInVal<ImageBundle>("Image");
|
|
if (val.loaded && !loaded) {
|
|
GLuint textureHandle;
|
|
glGenTextures(1, &textureHandle);
|
|
glBindTexture(GL_TEXTURE_2D, textureHandle);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
|
|
GL_CLAMP_TO_EDGE); // This is required on WebGL for non
|
|
// power-of-two textures
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,
|
|
GL_CLAMP_TO_EDGE); // Same
|
|
#if defined(GL_UNPACK_ROW_LENGTH) && !defined(__EMSCRIPTEN__)
|
|
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
|
#endif
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, val.width, val.height, 0, GL_RGBA,
|
|
GL_UNSIGNED_BYTE, val.image);
|
|
|
|
txHandle = textureHandle;
|
|
id = val.fname;
|
|
loaded = true;
|
|
prev_h = val.height;
|
|
prev_w = val.width;
|
|
}
|
|
if (!val.loaded && loaded) {
|
|
glDeleteTextures(1, &txHandle);
|
|
loaded = false;
|
|
}
|
|
|
|
if ((val.fname != id) && loaded) {
|
|
if (prev_w != val.width || prev_h != val.height) {
|
|
glDeleteTextures(1, &txHandle);
|
|
GLuint textureHandle;
|
|
glGenTextures(1, &textureHandle);
|
|
glBindTexture(GL_TEXTURE_2D, textureHandle);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
|
|
GL_CLAMP_TO_EDGE); // This is required on WebGL for non
|
|
// power-of-two textures
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,
|
|
GL_CLAMP_TO_EDGE); // Same
|
|
#if defined(GL_UNPACK_ROW_LENGTH) && !defined(__EMSCRIPTEN__)
|
|
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
|
#endif
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, val.width, val.height, 0,
|
|
GL_RGBA, GL_UNSIGNED_BYTE, val.image);
|
|
|
|
txHandle = textureHandle;
|
|
|
|
} else {
|
|
glBindTexture(GL_TEXTURE_2D, txHandle);
|
|
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, val.width, val.height, GL_RGBA,
|
|
GL_UNSIGNED_BYTE, val.image);
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
}
|
|
prev_h = val.height;
|
|
prev_w = val.width;
|
|
id = val.fname;
|
|
}
|
|
|
|
if (loaded) {
|
|
if (val.height < val.width) {
|
|
ImGui::Image((void *)(intptr_t)txHandle,
|
|
ImVec2(256, val.calcResizedWidth(256)));
|
|
} else {
|
|
ImGui::Image((void *)(intptr_t)txHandle,
|
|
ImVec2(val.calcResizedHeight(256), 256));
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
int prev_h = 0;
|
|
int prev_w = 0;
|
|
bool loaded = false;
|
|
GLuint txHandle;
|
|
std::string id;
|
|
};
|
|
|
|
class LoadImage : public ImFlow::BaseNode {
|
|
public:
|
|
ImageBundle bundle;
|
|
|
|
LoadImage() {
|
|
setTitle("Load Image");
|
|
setStyle(ImFlow::NodeStyle::brown());
|
|
addOUT<ImageBundle>("Image")->behaviour([this]() { return bundle; });
|
|
}
|
|
|
|
~LoadImage() {
|
|
if (bundle.loaded) {
|
|
stbi_image_free(bundle.image);
|
|
}
|
|
}
|
|
|
|
void draw() override {
|
|
LoadCallback lc = LoadCallback();
|
|
#ifdef __EMSCRIPTEN__
|
|
if (ImGui::Button("Select File")) {
|
|
emscripten_browser_file::upload(".png,.jpg,.jpeg", handle_upload_file,
|
|
this);
|
|
}
|
|
#else
|
|
if (bundle.fname == "") {
|
|
handle_upload_file_local(
|
|
"/Users/tanishqdubey/Downloads/Odd-eyed_cat_by_ihasb33r.jpg", this);
|
|
}
|
|
#endif
|
|
if (bundle.fname != "") {
|
|
bundle.loaded = true;
|
|
ImGui::Text("File Loaded");
|
|
} else {
|
|
ImGui::Text("No image loaded");
|
|
}
|
|
}
|
|
|
|
private:
|
|
bool showPreview = false;
|
|
|
|
static void handle_upload_file_local(const char *filename, void *callback) {
|
|
auto lc{reinterpret_cast<LoadImage *>(callback)};
|
|
const int channelCount = 4;
|
|
int imageFileChannelCount;
|
|
int width, height;
|
|
|
|
uint8_t *image = (uint8_t *)stbi_load(filename, &width, &height,
|
|
&imageFileChannelCount, channelCount);
|
|
|
|
if (image == NULL) {
|
|
fprintf(stderr, "%s\nFailed to open %s\n", stbi_failure_reason(),
|
|
lc->bundle.fname.c_str());
|
|
lc->bundle.fname = "";
|
|
}
|
|
|
|
lc->bundle.fname = uuid::generate_uuid_v4();
|
|
lc->bundle.width = width;
|
|
lc->bundle.height = height;
|
|
lc->bundle.channels = channelCount;
|
|
lc->bundle.image = image;
|
|
lc->bundle.loaded = true;
|
|
}
|
|
|
|
static void handle_upload_file(std::string const &filename,
|
|
std::string const &mime_type,
|
|
std::string_view buffer, void *callback) {
|
|
auto lc{reinterpret_cast<LoadImage *>(callback)};
|
|
const int channelCount = 4;
|
|
int imageFileChannelCount;
|
|
int width, height;
|
|
|
|
auto d = buffer.data();
|
|
stbi_uc *image = (stbi_uc *)stbi_load_from_memory(
|
|
(const stbi_uc *)buffer.data(), buffer.size(), &width, &height,
|
|
&imageFileChannelCount, channelCount);
|
|
|
|
if (image == NULL) {
|
|
fprintf(stderr, "%s\nFailed to open %s - %s\n", stbi_failure_reason(),
|
|
filename.c_str(), mime_type.c_str());
|
|
lc->bundle.fname = "";
|
|
}
|
|
|
|
lc->bundle.fname = uuid::generate_uuid_v4();
|
|
lc->bundle.width = width;
|
|
lc->bundle.height = height;
|
|
lc->bundle.channels = channelCount;
|
|
lc->bundle.image = image;
|
|
lc->bundle.loaded = true;
|
|
}
|
|
};
|