Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 101 additions & 2 deletions lib/aux_js.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,34 @@
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.

#include "visual.hpp"
#include "aux_vis.hpp"
#include "palettes.hpp"
#include "stream_reader.hpp"
#include "visual.hpp"

#ifdef GLVIS_USE_LIBPNG
#include <png.h>
#endif

#include <SDL2/SDL_hints.h>
#include <emscripten/bind.h>
#include <emscripten/html5.h>
#include <emscripten/val.h>

// used in extern context
thread_local std::string plot_caption;
thread_local std::string extra_caption;
thread_local mfem::GeometryRefiner GLVisGeometryRefiner;

static VisualizationSceneScalarData * vs = nullptr;

// either bitmap data or png bytes
std::vector<unsigned char> * screen_state = nullptr;

StreamState stream_state;

namespace em = emscripten;

namespace js
{

Expand Down Expand Up @@ -311,11 +324,93 @@ std::string getHelpString()
= dynamic_cast<VisualizationSceneScalarData*>(GetVisualizationScene());
return vss->GetHelpString();
}

em::val getScreenBuffer(bool flip_y=false)
{
MyExpose();
auto * wnd = GetAppWindow();
int w, h;
wnd->getGLDrawSize(w, h);

// 4 bytes for rgba
const size_t buffer_size = w*h*4;
if (screen_state == nullptr)
{
screen_state = new std::vector<unsigned char>(buffer_size);
}
else if (buffer_size > screen_state->size())
{
screen_state->resize(buffer_size);
}

glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, screen_state->data());

// `canvas` starts at top left but `glReadPixels' starts at bottom left
if (flip_y)
{
auto * orig = screen_state;
auto * flip = new std::vector<unsigned char>(buffer_size);
// from `glReadPixels` man page: Pixels are returned in row order from
// the lowest to the highest row, left to right in each row.
for (int j = 0; j < h; ++j)
{
for (int i = 0; i < w*4; ++i)
{
(*flip)[4*w*j + i] = (*orig)[4*w*(h-j-1) + i];
}
}
screen_state = flip;
delete orig;
}

return em::val(em::typed_memory_view(screen_state->size(),
screen_state->data()));
}

#ifdef GLVIS_USE_LIBPNG
em::val getPNGByteArray()
{
constexpr const char * filename = "im.png";
auto * wnd = GetAppWindow();
int w, h;
wnd->getGLDrawSize(w, h);

MyExpose();
// save to in-memory file
int status = SaveAsPNG(filename, w, h, wnd->isHighDpi(), true);
if (status != 0)
{
fprintf(stderr, "unable to generate png\n");
return em::val::null();
}

// load in-memory file to byte array
std::ifstream ifs(filename, std::ios::binary);
if (!ifs)
{
fprintf(stderr, "unable to load png\n");
return em::val::null();
}

if (!screen_state)
{
screen_state = new std::vector<unsigned char>(std::istreambuf_iterator<char>
(ifs), {});
}
else
{
screen_state->assign(std::istreambuf_iterator<char>
(ifs), {});
}

return em::val(em::typed_memory_view(screen_state->size(),
screen_state->data()));
}
#endif // GLVIS_USE_LIBPNG
} // namespace js

// Info on type conversion:
// https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html#built-in-type-conversions
namespace em = emscripten;
EMSCRIPTEN_BINDINGS(js_funcs)
{
em::function("startVisualization", &js::startVisualization);
Expand All @@ -334,4 +429,8 @@ EMSCRIPTEN_BINDINGS(js_funcs)
em::function("getHelpString", &js::getHelpString);
em::function("processKeys", &js::processKeys);
em::function("processKey", &js::processKey);
em::function("getScreenBuffer", &js::getScreenBuffer);
#ifdef GLVIS_USE_LIBPNG
em::function("getPNGByteArray", &js::getPNGByteArray);
#endif
}
128 changes: 70 additions & 58 deletions lib/aux_vis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -890,6 +890,74 @@ int InvertSurfaceVertical(SDL_Surface *surface)
return 0;
}

#ifdef GLVIS_USE_LIBPNG
int SaveAsPNG(const char *fname, int w, int h, bool is_hidpi, bool with_alpha)
{
png_byte *pixels = new png_byte[(with_alpha ? 4 : 3)*w];
if (!pixels)
{
return 1;
}

png_structp png_ptr =
png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png_ptr)
{
delete [] pixels;
return 1;
}
png_infop info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr)
{
png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
delete [] pixels;
return 1;
}

FILE *fp = fopen(fname, "wb");
if (!fp)
{
png_destroy_write_struct(&png_ptr, &info_ptr);
delete [] pixels;
return 2;
}

if (setjmp(png_jmpbuf(png_ptr)))
{
fclose(fp);
png_destroy_write_struct(&png_ptr, &info_ptr);
delete [] pixels;
return 3;
}

png_uint_32 ppi = is_hidpi ? 144 : 72; // pixels/inch
png_uint_32 ppm = ppi/0.0254 + 0.5; // pixels/meter
png_set_pHYs(png_ptr, info_ptr, ppm, ppm, PNG_RESOLUTION_METER);

png_init_io(png_ptr, fp);
png_set_IHDR(png_ptr, info_ptr, w, h, 8,
with_alpha ? PNG_COLOR_TYPE_RGBA : PNG_COLOR_TYPE_RGB,
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
PNG_FILTER_TYPE_DEFAULT);


png_write_info(png_ptr, info_ptr);
for (int i = 0; i < h; i++)
{
glReadPixels(0, h-1-i, w, 1, with_alpha ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE,
pixels);
png_write_row(png_ptr, pixels);
}
png_write_end(png_ptr, info_ptr);

fclose(fp);
png_destroy_write_struct(&png_ptr, &info_ptr);
delete [] pixels;

return 0;
}
#endif // GLVIS_USE_LIBPNG

int Screenshot(const char *fname, bool convert)
{
#ifdef GLVIS_DEBUG
Expand Down Expand Up @@ -987,64 +1055,8 @@ int Screenshot(const char *fname, bool convert)

#elif defined(GLVIS_USE_LIBPNG)
// Save as png image. Requires libpng.

png_byte *pixels = new png_byte[3*w];
if (!pixels)
{
return 1;
}

png_structp png_ptr =
png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png_ptr)
{
delete [] pixels;
return 1;
}
png_infop info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr)
{
png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
delete [] pixels;
return 1;
}

FILE *fp = fopen(filename.c_str(), "wb");
if (!fp)
{
png_destroy_write_struct(&png_ptr, &info_ptr);
delete [] pixels;
return 2;
}

if (setjmp(png_jmpbuf(png_ptr)))
{
fclose(fp);
png_destroy_write_struct(&png_ptr, &info_ptr);
delete [] pixels;
return 3;
}

png_uint_32 ppi = wnd->isHighDpi() ? 144 : 72; // pixels/inch
png_uint_32 ppm = ppi/0.0254 + 0.5; // pixels/meter
png_set_pHYs(png_ptr, info_ptr, ppm, ppm, PNG_RESOLUTION_METER);

png_init_io(png_ptr, fp);
png_set_IHDR(png_ptr, info_ptr, w, h, 8, PNG_COLOR_TYPE_RGB,
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
PNG_FILTER_TYPE_DEFAULT);

png_write_info(png_ptr, info_ptr);
for (int i = 0; i < h; i++)
{
glReadPixels(0, h-1-i, w, 1, GL_RGB, GL_UNSIGNED_BYTE, pixels);
png_write_row(png_ptr, pixels);
}
png_write_end(png_ptr, info_ptr);

fclose(fp);
png_destroy_write_struct(&png_ptr, &info_ptr);
delete [] pixels;
int status = SaveAsPNG(filename.c_str(), w, h, wnd->isHighDpi());
if (status != 0) { return status; }

#else
// use SDL for screenshots
Expand Down
4 changes: 4 additions & 0 deletions lib/aux_vis.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ void SetWindowTitle(const char *title);

/// Take a screenshot using libtiff, libpng or sdl2
int Screenshot(const char *fname, bool convert = false);
#ifdef GLVIS_USE_LIBPNG
int SaveAsPNG(const char *fname, int w, int h, bool is_hidpi,
bool with_alpha=false);
#endif

/// Send a sequence of keystrokes to the visualization window
void SendKeySequence(const char *seq);
Expand Down
6 changes: 3 additions & 3 deletions makefile
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,13 @@ GLVIS_LDFLAGS ?=
# emcc is used when building the wasm/js version
EMCC ?= emcc -std=c++11
FONT_FILE ?= OpenSans.ttf
EMCC_OPTS ?= -s USE_SDL=2 -s USE_FREETYPE=1
EMCC_OPTS ?= -s USE_SDL=2 -s USE_FREETYPE=1 -s USE_LIBPNG=1
# TODO: we don't want to have DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=0
# longterm but until the SDL layer supports non-default canvas ids we need this
EMCC_LIBS ?= -s USE_SDL=2 --bind -s ALLOW_MEMORY_GROWTH=1 -s SINGLE_FILE=1 \
--no-heap-copy -s ENVIRONMENT=web -s MODULARIZE=1 -s EXPORT_NAME=glvis \
-s GL_ASSERTIONS=1 -s GL_DEBUG=1 -s USE_FREETYPE=1 -s MAX_WEBGL_VERSION=2 \
-s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=0
-s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=0 --minify 0
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm just going to add this so we don't have to patch each time for the web build


# Flags used when $(GLVIS_DEBUG) is not the same as $(MFEM_DEBUG)
CXX11FLAG ?= -std=c++11
Expand Down Expand Up @@ -266,7 +266,7 @@ BYTECODE_FILES = $(WEB_SOURCE_FILES:.cpp=.bc)
$(CCC) -o $@ -c $<

%.bc: %.cpp
$(EMCC) $(EMCC_OPTS) -c $< -o $@
$(EMCC) $(EMCC_OPTS) $(GLVIS_FLAGS) -c $< -o $@

glvis: glvis.cpp lib/libglvis.a $(CONFIG_MK) $(MFEM_LIB_FILE)
$(CCC) -o glvis glvis.cpp -Llib -lglvis $(LIBS)
Expand Down