going public
This commit is contained in:
commit
be7d9edf91
84 changed files with 85206 additions and 0 deletions
87
src/Artboard.cpp
Normal file
87
src/Artboard.cpp
Normal file
|
@ -0,0 +1,87 @@
|
|||
#include "Artboard.h"
|
||||
|
||||
namespace VariableEditor {
|
||||
void Artboard::setup(){
|
||||
props.width = ofGetWidth();
|
||||
props.height = ofGetHeight();
|
||||
|
||||
position = glm::vec3(0);
|
||||
|
||||
fboSettings.width = makeEven(props.width * props.pixelDensity);
|
||||
fboSettings.height = makeEven(props.height * props.pixelDensity);
|
||||
fboSettings.numSamples = 0;
|
||||
fboSettings.internalformat = GL_RGBA;
|
||||
fboSettings.minFilter = GL_LINEAR_MIPMAP_LINEAR;
|
||||
fboSettings.maxFilter = GL_NEAREST;
|
||||
//fboSettings.textureTarget = GL_TEXTURE_2D_MULTISAMPLE;
|
||||
fbo.allocate(fboSettings);
|
||||
|
||||
fbo.begin();
|
||||
ofClear(ofFloatColor(props.backgroundColor[0],
|
||||
props.backgroundColor[1],
|
||||
props.backgroundColor[2],
|
||||
props.backgroundColor[3]));
|
||||
fbo.end();
|
||||
}
|
||||
void Artboard::setProps(const ArtboardProps & props){
|
||||
if(props.width != this->props.width
|
||||
|| props.height != this->props.height
|
||||
|| props.pixelDensity != this->props.pixelDensity){
|
||||
|
||||
ofFbo newFbo;
|
||||
fboSettings.width = makeEven(props.width * props.pixelDensity);
|
||||
fboSettings.height = makeEven(props.height * props.pixelDensity);
|
||||
newFbo.allocate(fboSettings);
|
||||
newFbo.resetAnchor();
|
||||
newFbo.begin();
|
||||
ofClear(ofFloatColor(props.backgroundColor[0],
|
||||
props.backgroundColor[1],
|
||||
props.backgroundColor[2],
|
||||
props.backgroundColor[3]));
|
||||
newFbo.end();
|
||||
fbo = std::move(newFbo);
|
||||
}
|
||||
position = glm::vec3(props.x, props.y, 0);
|
||||
this->props = props;
|
||||
}
|
||||
ArtboardProps Artboard::getProps(){
|
||||
return props;
|
||||
}
|
||||
void Artboard::setPosition(float x,
|
||||
float y,
|
||||
float z){
|
||||
position = glm::vec3(x, y, z);
|
||||
}
|
||||
glm::ivec2 Artboard::getShape(){
|
||||
return glm::ivec2(props.width, props.height);
|
||||
}
|
||||
void Artboard::begin(){
|
||||
fbo.begin();
|
||||
glEnable(GL_BLEND);
|
||||
glBlendEquation(GL_FUNC_ADD);
|
||||
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE);
|
||||
ofPushStyle();
|
||||
ofFill();
|
||||
ofSetColor(ofFloatColor(props.backgroundColor[0],
|
||||
props.backgroundColor[1],
|
||||
props.backgroundColor[2],
|
||||
props.backgroundColor[3]));
|
||||
ofDrawRectangle(0, 0, fbo.getWidth(), fbo.getHeight());
|
||||
ofPopStyle();
|
||||
|
||||
}
|
||||
void Artboard::end(){
|
||||
fbo.getTexture().generateMipmap();
|
||||
fbo.end();
|
||||
}
|
||||
void Artboard::draw(){
|
||||
ofPushMatrix();
|
||||
ofTranslate(position);
|
||||
ofScale(props.zoom);
|
||||
fbo.draw(0, 0, makeEven(fbo.getWidth() / props.pixelDensity), makeEven(fbo.getHeight() / props.pixelDensity));
|
||||
ofPopMatrix();
|
||||
}
|
||||
const ofFbo & Artboard::getFbo(){
|
||||
return fbo;
|
||||
}
|
||||
}
|
39
src/Artboard.h
Normal file
39
src/Artboard.h
Normal file
|
@ -0,0 +1,39 @@
|
|||
#pragma once
|
||||
|
||||
#include "ofMain.h"
|
||||
|
||||
namespace VariableEditor {
|
||||
struct ArtboardProps {
|
||||
std::array <float, 4> backgroundColor = {1.0, 1.0, 1.0, 1.0};
|
||||
int x;
|
||||
int y;
|
||||
int width;
|
||||
int height;
|
||||
float zoom;
|
||||
float pixelDensity = 1.0;
|
||||
};
|
||||
|
||||
class Artboard {
|
||||
int makeEven(float n){
|
||||
n = n + 0.5 - (n < 0);
|
||||
int nr = int(n);
|
||||
return nr - nr % 2;
|
||||
}
|
||||
public:
|
||||
void setup();
|
||||
void setProps(const ArtboardProps & props);
|
||||
ArtboardProps getProps();
|
||||
void setPosition(float x, float y, float z);
|
||||
glm::ivec2 getShape();
|
||||
void begin();
|
||||
void end();
|
||||
void draw();
|
||||
const ofFbo & getFbo();
|
||||
|
||||
private:
|
||||
ArtboardProps props;
|
||||
ofFboSettings fboSettings;
|
||||
ofFbo fbo;
|
||||
glm::vec3 position;
|
||||
};
|
||||
}
|
1
src/Exporter.cpp
Normal file
1
src/Exporter.cpp
Normal file
|
@ -0,0 +1 @@
|
|||
#include "Exporter.h"
|
8
src/Exporter.h
Normal file
8
src/Exporter.h
Normal file
|
@ -0,0 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include "Zip.h"
|
||||
|
||||
namespace VariableEditor {
|
||||
class Exporter {
|
||||
};
|
||||
}
|
52
src/Zip.cpp
Normal file
52
src/Zip.cpp
Normal file
|
@ -0,0 +1,52 @@
|
|||
#include "Zip.h"
|
||||
#include "zip/zip.h"
|
||||
|
||||
namespace VariableEditor {
|
||||
|
||||
Zip::Zip(const std::string & filename){
|
||||
//zip = zip_open(filename.c_str(), ZIP_DEFAULT_COMPRESSION_LEVEL, 'w');
|
||||
zip = zip_stream_open(NULL, 0, ZIP_DEFAULT_COMPRESSION_LEVEL, 'w');
|
||||
}
|
||||
|
||||
Zip::~Zip(){
|
||||
if(!closed){
|
||||
this->close();
|
||||
}
|
||||
}
|
||||
|
||||
void Zip::addFile(const std::string & filename){
|
||||
zip_entry_open(zip, filename.c_str());
|
||||
{
|
||||
zip_entry_fwrite(zip, filename.c_str());
|
||||
}
|
||||
zip_entry_close(zip);
|
||||
}
|
||||
|
||||
void Zip::addBuffer(std::string filename, char * inbuf, size_t inbufsize){
|
||||
zip_entry_open(zip, filename.c_str());
|
||||
{
|
||||
zip_entry_write(zip, inbuf, inbufsize);
|
||||
}
|
||||
zip_entry_close(zip);
|
||||
}
|
||||
|
||||
void Zip::getOutputBuffer(char * * outbuf, size_t & outbufsize){
|
||||
zip_stream_copy(zip, (void * *)outbuf, &outbufsize);
|
||||
}
|
||||
|
||||
void Zip::close(){
|
||||
std::cout << "close zip" << std::endl;
|
||||
zip_stream_close(zip);
|
||||
closed = true;
|
||||
}
|
||||
|
||||
UnZip::UnZip(const std::string & filename, const std::string & outdir) {
|
||||
//zip = zip_open(filename.c_str(), ZIP_DEFAULT_COMPRESSION_LEVEL, 'r');
|
||||
zip_extract(filename.c_str(), outdir.c_str(), NULL, NULL);
|
||||
//zip_stream_extract(inbuf, inbufsize, outdir.c_str(), NULL, NULL);
|
||||
//free(inbuf);
|
||||
}
|
||||
|
||||
UnZip::~UnZip() {};
|
||||
|
||||
}
|
32
src/Zip.h
Normal file
32
src/Zip.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
#pragma once
|
||||
|
||||
#include "zip/zip.h"
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include "ofMain.h"
|
||||
|
||||
namespace VariableEditor {
|
||||
class Zip {
|
||||
public:
|
||||
Zip(const std::string & filename = "lol.zip");
|
||||
~Zip();
|
||||
//void open(const std::string & filename);
|
||||
void addFile(const std::string & filename);
|
||||
void addBuffer(std::string filename, char * inbuf, size_t inbufsize);
|
||||
void getOutputBuffer(char * * outbuf, size_t & outbufsize);
|
||||
void close();
|
||||
private:
|
||||
struct zip_t * zip;
|
||||
bool closed = false;
|
||||
};
|
||||
|
||||
class UnZip {
|
||||
public:
|
||||
UnZip(const std::string & filename, const std::string & outdir);
|
||||
~UnZip();
|
||||
private:
|
||||
struct zip_t * zip;
|
||||
bool closed = false;
|
||||
};
|
||||
|
||||
}
|
2
src/emscripten-browser-file/.gitignore
vendored
Normal file
2
src/emscripten-browser-file/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
*.swp
|
||||
*.swo
|
21
src/emscripten-browser-file/LICENSE
Normal file
21
src/emscripten-browser-file/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2023 Armchair-Software
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
131
src/emscripten-browser-file/README.md
Normal file
131
src/emscripten-browser-file/README.md
Normal file
|
@ -0,0 +1,131 @@
|
|||
# Emscripten Browser File Library
|
||||
|
||||
Header-only C++ library to receive files from, and offer files to, the browser the Emscripten program is running in. Compact implementation in a single header file.
|
||||
|
||||
Intended for use in Emscripten code, this enables the user to "upload" files to your program using their native file selector, and to "download" files from your program to save to disk, as if they were interacting with a remote website filesystem.
|
||||
|
||||
See also [tar_to_stream.h](https://github.com/Armchair-Software/tar_to_stream), to tarball multiple files in memory for a single download.
|
||||
|
||||
## Use cases:
|
||||
|
||||
* Implement an "upload" function, that enables users to choose a file using their browser's native file selector - this file is read directly into your program's memory.
|
||||
* Candidate files can be filtered as with an "accept" attribute, identical to [`<input>` elements with `type="file"`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file).
|
||||
* Implement a "download" function, that allows you to create a "file" in memory, and offer it for "download" using the browser's native file-save dialogue.
|
||||
* Filename and MIME type can be specified.
|
||||
|
||||
## Functionality
|
||||
|
||||
* `emscripten_browser_file::download()` - your program shares a section of memory, and the user receives it as a file they can save
|
||||
* `emscripten_browser_file::upload()` - user selects a file on their filesystem, and your program receives the contents in memory
|
||||
|
||||
### Download
|
||||
|
||||
From the user's point of view, the `download` function acts as if the user has chosen to download a file from the web. In this case, you define a buffer referencing data in memory and specify a filename and MIME type, and the user's browser either shows a "save as" interface asking where the file should be saved, or saves it to their default save location, as per their browser preferences.
|
||||
|
||||
#### Example
|
||||
|
||||
```cpp
|
||||
#include <emscripten_browser_file.h>
|
||||
|
||||
auto main()->int {
|
||||
std::string filename{"hello_world.txt"};
|
||||
std::string mime_type{"application/text/plain"};
|
||||
std::string data{"Hello world!\n"};
|
||||
emscripten_browser_file::download(filename, mime_type, data);
|
||||
}
|
||||
```
|
||||
|
||||
The download call takes the following arguments:
|
||||
```cpp
|
||||
emscripten_browser_file::download(
|
||||
std::string const &filename, // the default filename for the browser to save. Note that browsers do not have to honour this, and may choose to mangle it
|
||||
std::string const &mime_type, // the MIME type of the data, treated as if it were a webserver serving a file
|
||||
std::string_view buffer // a buffer describing the data to download - can be any array of bytes, passed as a string_view
|
||||
) {
|
||||
```
|
||||
|
||||
`download` also has an override accepting `std::string` instead of `char const*`.
|
||||
|
||||
For files containing binary data, you will usually want to use the MIME type `application/octet-stream`.
|
||||
|
||||
### Upload
|
||||
From the user's point of view, the `upload` function acts as if the user is uploading a file to a remote website. In this case, the file is loaded into a buffer in memory (referred to by a `std::string_view`) that is accessible to a C++ callback function you define.
|
||||
|
||||
#### Example
|
||||
|
||||
```cpp
|
||||
#include <emscripten_browser_file.h>
|
||||
|
||||
void handle_upload_file(std::string const &filename, std::string const &mime_type, std::string_view buffer, void*) {
|
||||
// define a handler to process the file
|
||||
// ...
|
||||
}
|
||||
|
||||
auto main()->int {
|
||||
// open the browser's file selector, and pass the file to the upload handler
|
||||
emscripten_browser_file::upload(".png,.jpg,.jpeg", handle_upload_file);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
The upload call takes the following arguments:
|
||||
```cpp
|
||||
emscripten_browser_file::upload(
|
||||
char const *accept_types, // an "accept" attribute, listing what file types can be accepted - see: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#unique_file_type_specifiers
|
||||
upload_handler callback, // a callback function to call with the received data
|
||||
void *callback_data = nullptr, // optional pointer to pass to your callback function
|
||||
);
|
||||
```
|
||||
`upload` also has an override accepting `std::string` instead of `char const*`.
|
||||
|
||||
The callback must have the following signature:
|
||||
|
||||
```cpp
|
||||
void handle_upload_file(
|
||||
std::string const &filename, // the filename of the file the user selected
|
||||
std::string const &mime_type, // the MIME type of the file the user selected, for example "image/png"
|
||||
std::string_view buffer, // the file's content is exposed in this string_view - access the data with buffer.data() and size with buffer.size().
|
||||
void *callback_data = nullptr // optional callback data - identical to whatever you passed to handle_upload_file()
|
||||
);
|
||||
```
|
||||
|
||||
#### Using callback data
|
||||
|
||||
The callback can receive additional data through a void pointer passed to the `upload` function:
|
||||
|
||||
```cpp
|
||||
#include <emscripten_browser_file.h>
|
||||
#include <iostream>
|
||||
|
||||
void handle_upload_file(std::string const &filename, std::string const &mime_type, std::string_view buffer, void *callback_data) {
|
||||
// define a handler to process the file
|
||||
auto my_data{*reintrepret_cast<std::string*>(my_data)};
|
||||
std::cout << "Received callback data: " << my_data << std::endl;
|
||||
}
|
||||
|
||||
auto main()->int {
|
||||
std::string my_data{"hello world"};
|
||||
auto my_data_ptr{reintrepret_cast<void*>(&my_data)};
|
||||
|
||||
// pass callback data to the handler
|
||||
emscripten_browser_file::upload(".png,.jpg,.jpeg", handle_upload_file, my_data_ptr);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
You can use this to pass shared state, or any other data to the callback function - for example an instance of a class whose member function should be called to deal with the received data.
|
||||
|
||||
## Building
|
||||
|
||||
Necessary emsripten link flags:
|
||||
|
||||
- Building with emscripten will require you to pass, if you do not already do so, `-sEXPORTED_RUNTIME_METHODS=[ccall]` at the link stage.
|
||||
- This uses dynamic memory allocation, so you need `-sALLOW_MEMORY_GROWTH=1` at the link stage.
|
||||
- Depending on your optimisation settings, the compiler may remove JS `malloc` and `free` functions (this happens with `-O3` at the time of writing, see [emscripten issue 6882](https://github.com/emscripten-core/emscripten/issues/6882)). This can be avoided by explicitly exporting those functions: add `-sEXPORTED_FUNCTIONS=[_main,_malloc,_free]` at the link stage.
|
||||
|
||||
## Other useful libraries
|
||||
|
||||
You may also find the following Emscripten helper libraries useful:
|
||||
|
||||
- [Emscripten Browser Clipboard Library](https://github.com/Armchair-Software/emscripten-browser-clipboard) - easy handling of browser copy and paste events in your C++ code.
|
||||
- [Emscripten Browser Cursor](https://github.com/Armchair-Software/emscripten-browser-cursor) - easy manipulation of browser mouse pointer cursors from C++.
|
101
src/emscripten-browser-file/emscripten_browser_file.h
Normal file
101
src/emscripten-browser-file/emscripten_browser_file.h
Normal file
|
@ -0,0 +1,101 @@
|
|||
#ifndef EMSCRIPTEN_UPLOAD_FILE_H_INCLUDED
|
||||
#define EMSCRIPTEN_UPLOAD_FILE_H_INCLUDED
|
||||
|
||||
#include <string>
|
||||
#include <emscripten.h>
|
||||
|
||||
#define _EM_JS_INLINE(ret, c_name, js_name, params, code) \
|
||||
extern "C" { \
|
||||
ret c_name params EM_IMPORT(js_name); \
|
||||
EMSCRIPTEN_KEEPALIVE \
|
||||
__attribute__((section("em_js"), aligned(1))) inline char __em_js__##js_name[] = \
|
||||
#params "<::>" code; \
|
||||
}
|
||||
|
||||
#define EM_JS_INLINE(ret, name, params, ...) _EM_JS_INLINE(ret, name, name, params, #__VA_ARGS__)
|
||||
|
||||
namespace emscripten_browser_file {
|
||||
|
||||
/////////////////////////////////// Interface //////////////////////////////////
|
||||
|
||||
using upload_handler = void(*)(std::string const&, std::string const&, std::string_view buffer, void*);
|
||||
|
||||
inline void upload(std::string const &accept_types, upload_handler callback, void *callback_data = nullptr);
|
||||
inline void download(std::string const &filename, std::string const &mime_type, std::string_view buffer);
|
||||
|
||||
///////////////////////////////// Implementation ///////////////////////////////
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wmissing-variable-declarations"
|
||||
EM_JS_INLINE(void, upload, (char const *accept_types, upload_handler callback, void *callback_data), {
|
||||
/// Prompt the browser to open the file selector dialogue, and pass the file to the given handler
|
||||
/// Accept-types are in the format ".png,.jpeg,.jpg" as per https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept
|
||||
/// Upload handler callback signature is:
|
||||
/// void my_handler(std::string const &filename, std::string const &mime_type, std::string_view buffer, void *callback_data = nullptr);
|
||||
globalThis["open_file"] = function(e) {
|
||||
const file_reader = new FileReader();
|
||||
file_reader.onload = (event) => {
|
||||
const uint8Arr = new Uint8Array(event.target.result);
|
||||
const num_bytes = uint8Arr.length * uint8Arr.BYTES_PER_ELEMENT;
|
||||
const data_ptr = Module["_malloc"](num_bytes);
|
||||
const data_on_heap = new Uint8Array(Module["HEAPU8"].buffer, data_ptr, num_bytes);
|
||||
data_on_heap.set(uint8Arr);
|
||||
Module["ccall"]('upload_file_return', 'number', ['string', 'string', 'number', 'number', 'number', 'number'], [event.target.filename, event.target.mime_type, data_on_heap.byteOffset, uint8Arr.length, callback, callback_data]);
|
||||
Module["_free"](data_ptr);
|
||||
};
|
||||
file_reader.filename = e.target.files[0].name;
|
||||
file_reader.mime_type = e.target.files[0].type;
|
||||
file_reader.readAsArrayBuffer(e.target.files[0]);
|
||||
};
|
||||
|
||||
var file_selector = document.createElement('input');
|
||||
file_selector.setAttribute('type', 'file');
|
||||
file_selector.setAttribute('onchange', 'globalThis["open_file"](event)');
|
||||
file_selector.setAttribute('accept', UTF8ToString(accept_types));
|
||||
file_selector.click();
|
||||
});
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
inline void upload(std::string const &accept_types, upload_handler callback, void *callback_data) {
|
||||
/// C++ wrapper for javascript upload call
|
||||
upload(accept_types.c_str(), callback, callback_data);
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wmissing-variable-declarations"
|
||||
EM_JS_INLINE(void, download, (char const *filename, char const *mime_type, void const *buffer, size_t buffer_size), {
|
||||
/// Offer a buffer in memory as a file to download, specifying download filename and mime type
|
||||
var a = document.createElement('a');
|
||||
a.download = UTF8ToString(filename);
|
||||
var bufferCopy = new ArrayBuffer(buffer_size);
|
||||
var uint8Array = new Uint8Array(bufferCopy);
|
||||
uint8Array.set(new Uint8Array(Module["HEAPU8"].buffer, buffer, buffer_size));
|
||||
a.href = URL.createObjectURL(new Blob([uint8Array], {type: UTF8ToString(mime_type)}));
|
||||
a.click();
|
||||
});
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
inline void download(std::string const &filename, std::string const &mime_type, std::string_view buffer) {
|
||||
/// C++ wrapper for javascript download call, accepting a string_view
|
||||
download(filename.c_str(), mime_type.c_str(), buffer.data(), buffer.size());
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
extern "C" {
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE inline int upload_file_return(char const *filename, char const *mime_type, char *buffer, size_t buffer_size, upload_handler callback, void *callback_data);
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE inline int upload_file_return(char const *filename, char const *mime_type, char *buffer, size_t buffer_size, upload_handler callback, void *callback_data) {
|
||||
/// Load a file - this function is called from javascript when the file upload is activated
|
||||
callback(filename, mime_type, {buffer, buffer_size}, callback_data);
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // EMSCRIPTEN_UPLOAD_FILE_H_INCLUDED
|
388
src/main.cpp
Normal file
388
src/main.cpp
Normal file
|
@ -0,0 +1,388 @@
|
|||
#include "Artboard.h"
|
||||
#include "Layer.h"
|
||||
#include "Utils.h"
|
||||
#include "import-font.h"
|
||||
#include "ofFileUtils.h"
|
||||
#include "ofMain.h"
|
||||
#include "ofApp.h"
|
||||
#include "ofUtils.h"
|
||||
#include "ofxProfiler.h"
|
||||
#include "ofWindowSettings.h"
|
||||
#include <string>
|
||||
|
||||
//========================================================================
|
||||
// MAIN APP
|
||||
// shared_ptr to main app
|
||||
// this is global, so we can call it in our binding functions
|
||||
|
||||
shared_ptr <VariableEditor::ofApp> app;
|
||||
|
||||
//========================================================================
|
||||
// EMSCRIPTEN BINDING FUNCTIONS
|
||||
// here we add functions to be called from javascript
|
||||
#ifdef TARGET_EMSCRIPTEN
|
||||
|
||||
string addExistingLayer(ofxVariableLab::Layer::Props props,
|
||||
string layerID){
|
||||
if(props.fontPath == ""){ // TODO: cleaner handling
|
||||
ofxVariableLab::Layer::Props p;
|
||||
props.fontPath = p.fontPath;
|
||||
}
|
||||
return app->layerComposition.addLayer(props,
|
||||
layerID);
|
||||
}
|
||||
string addNewLayer(){
|
||||
ofxVariableLab::Layer::Props props;
|
||||
return app->layerComposition.addLayer(props);
|
||||
}
|
||||
void removeLayer(string layerID){
|
||||
app->layerComposition.removeLayer(layerID);
|
||||
}
|
||||
vector <string> debug_getLayers(){
|
||||
vector <string> out;
|
||||
auto & layersMap = app->layerComposition.getLayers();
|
||||
for(const auto & [k, v] : layersMap){
|
||||
out.push_back(k);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
void debug_downloadFbo(string name){
|
||||
if(name == "artboard"){
|
||||
app->downloadFboAsImage("artboard" + ofGetTimestampString() + ".png", app->artboard.getFbo());
|
||||
}else if(name == "fbo"){
|
||||
app->downloadFboAsImage("fbo" + ofGetTimestampString() + ".png", app->fbo);
|
||||
}
|
||||
}
|
||||
ofxVariableLab::Layer::Props getProps(string layerID){
|
||||
return app->layerComposition.getLayer(layerID)->getProps();
|
||||
}
|
||||
bool setProps(ofxVariableLab::Layer::Props props,
|
||||
string layerID){
|
||||
props.letterDelay /= app->timeScale;
|
||||
for(const auto & [k, v]: props.letterDelays){
|
||||
props.letterDelays[k] /= app->timeScale;
|
||||
}
|
||||
app->layerComposition.getLayer(layerID)->setProps(props);
|
||||
return true; // TODO: do not always return true maybe
|
||||
}
|
||||
VariableEditor::ArtboardProps getArtboardProps(){
|
||||
return app->artboard.getProps();
|
||||
}
|
||||
bool setArtboardProps(VariableEditor::ArtboardProps props){
|
||||
app->artboard.setProps(props);
|
||||
return true; // TODO: do not always return true maybe
|
||||
}
|
||||
ofxVariableLab::Layer::BoundingBox getBoundingBox(string layerID){
|
||||
return app->layerComposition.getLayer(layerID)->getBoundingBox();
|
||||
}
|
||||
void resetLetterDelay(){
|
||||
for(const auto & l: app->layerComposition.getLayers()){
|
||||
l.second->clearPropsBuffer();
|
||||
}
|
||||
}
|
||||
void setLayerOrder(vector <string> _layerOrder){
|
||||
vector <ofxVariableLab::LayerID> layerOrder;
|
||||
for(int i = 0; i < _layerOrder.size(); i++){
|
||||
ofxVariableLab::LayerID layer = _layerOrder[i];
|
||||
layerOrder.push_back(layer);
|
||||
}
|
||||
app->layerComposition.setLayerOrder(_layerOrder);
|
||||
}
|
||||
void setPlaying(bool playing){
|
||||
app->setPlaying(playing);
|
||||
}
|
||||
// TODO: rename this to exportRendering or sth similar
|
||||
void setRendering(bool rendering){
|
||||
ofDirectory exportDir(app->settings.tmpExportDir + "/frames");
|
||||
if(!exportDir.exists()){
|
||||
exportDir.create(true);
|
||||
}
|
||||
if(rendering){
|
||||
app->currentFrame = 0;
|
||||
app->recordedFrameNames.clear();
|
||||
exportDir.listDir();
|
||||
for(ofFile file : exportDir.getFiles()){
|
||||
//file.removeFile(file.getAbsolutePath(), false);
|
||||
file.remove();
|
||||
}
|
||||
double timelineLenth_seconds = EM_ASM_DOUBLE({
|
||||
return window.tp.core.val(window.tp.sheet.sequence.pointer.length);
|
||||
});
|
||||
double fps = 30.0;
|
||||
app->guessedFrameCount = std::ceil((fps * timelineLenth_seconds) / app->timeScale);
|
||||
app->timelineLength_seconds = timelineLenth_seconds;
|
||||
}
|
||||
app->rendering = rendering;
|
||||
}
|
||||
|
||||
bool getRendering(){
|
||||
return app->rendering;
|
||||
}
|
||||
|
||||
void renderNextFrame(){
|
||||
app->renderNextFrame = true;
|
||||
}
|
||||
|
||||
void setTimeScale(float timeScale){
|
||||
app->timeScale = timeScale;
|
||||
}
|
||||
|
||||
std::string importProjectAsZip(std::string filepath, std::string outdir){
|
||||
ofDisableDataPath();
|
||||
ofDirectory dir(outdir);
|
||||
if(!dir.exists()){
|
||||
dir.createDirectory(outdir);
|
||||
}
|
||||
VariableEditor::UnZip unzip(filepath, outdir);
|
||||
dir.listDir();
|
||||
|
||||
ofDirectory userFonts("/idbfs/fonts");
|
||||
|
||||
nlohmann::json json;
|
||||
json["files"] = nlohmann::json::array();
|
||||
for(int i = 0; i < dir.size(); i++){
|
||||
ofFile file = dir.getFile(i);
|
||||
if(ofToLower(file.getAbsolutePath()).find("project.json") != std::string::npos){
|
||||
try{
|
||||
std::ifstream project(file.getAbsolutePath());
|
||||
project >> json["project"];
|
||||
}
|
||||
catch(nlohmann::json::exception & e){
|
||||
std::cerr << "Module::importProjectAsZip " << e.what() << std::endl;
|
||||
}
|
||||
|
||||
}
|
||||
if(ofToLower(file.getExtension()) == "ttf"
|
||||
|| ofToLower(file.getExtension()) == "otf"){
|
||||
if(!file.moveTo(userFonts)){
|
||||
std::cout << "Module::importProjectAsZip" << " - cannot move font " << file.getFileName();
|
||||
}
|
||||
}else{
|
||||
file.remove();
|
||||
}
|
||||
json["files"].push_back(dir.getPath(i));
|
||||
}
|
||||
EM_ASM({
|
||||
FS.syncfs(false, function(err){
|
||||
});
|
||||
});
|
||||
return json.dump();
|
||||
}
|
||||
|
||||
void exportFramesAsZip(string projectName){
|
||||
app->downloadFramesAsZip(projectName);
|
||||
}
|
||||
|
||||
void downloadProject(string projectName,
|
||||
string projectJsonString){
|
||||
app->downloadProject(projectName,
|
||||
projectJsonString);
|
||||
}
|
||||
|
||||
std::vector <std::string> listAvailableFonts(){
|
||||
vector <std::string> out;
|
||||
ofEnableDataPath();
|
||||
ofDirectory rootDir(".");
|
||||
rootDir.listDir();
|
||||
{
|
||||
ofDirectory fontsDir("fonts");
|
||||
fontsDir.sort();
|
||||
fontsDir.allowExt("ttf");
|
||||
fontsDir.allowExt("otf");
|
||||
fontsDir.listDir();
|
||||
for(int i = 0; i < fontsDir.size(); i++){
|
||||
std::string fontPath = "data/" + fontsDir.getPath(i);
|
||||
out.push_back(fontPath);
|
||||
}
|
||||
}
|
||||
{
|
||||
ofDirectory fontsDir("free-fonts");
|
||||
fontsDir.sort();
|
||||
fontsDir.allowExt("ttf");
|
||||
fontsDir.allowExt("otf");
|
||||
fontsDir.listDir();
|
||||
for(int i = 0; i < fontsDir.size(); i++){
|
||||
std::string fontPath = "data/" + fontsDir.getPath(i);
|
||||
out.push_back(fontPath);
|
||||
}
|
||||
}
|
||||
ofDirectory idbfsDir("/idbfs");
|
||||
if(idbfsDir.exists()){
|
||||
ofDirectory userFonts("/idbfs/fonts");
|
||||
if(userFonts.exists()){
|
||||
userFonts.sort();
|
||||
userFonts.allowExt("ttf");
|
||||
userFonts.allowExt("otf");
|
||||
userFonts.listDir();
|
||||
for(int i = 0; i < userFonts.size(); i++){
|
||||
std::string fontPath = userFonts.getPath(i);
|
||||
ofStringReplace(fontPath, "/idbfs/", "idbfs/");
|
||||
out.push_back(fontPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
std::vector <ofxVariableLab::FontVariationAxis> listVariationAxes(std::string fontPath){
|
||||
std::vector <ofxVariableLab::FontVariationAxis> axes;
|
||||
ofxVariableLab::listFontVariationAxes(fontPath, axes);
|
||||
return axes;
|
||||
}
|
||||
|
||||
void windowResized(int w, int h){
|
||||
app->windowResized(w, h);
|
||||
}
|
||||
|
||||
void setUniform1f(string name, float value){
|
||||
cout << "VariableEditor::setUniform1f " << name << ": " << ofToString(value) << endl;
|
||||
app->layerComposition.setUniform1fv({name}, {value});
|
||||
}
|
||||
|
||||
void setUniform1i(string name, int value){
|
||||
app->layerComposition.setUniform1iv({name}, {value});
|
||||
}
|
||||
|
||||
void startProfiling(){
|
||||
#if OFX_PROFILER
|
||||
OFX_PROFILER_BEGIN_SESSION("profiling", "results.json");
|
||||
#else
|
||||
std::cout << "not compiled with internal profiling support" << endl;
|
||||
std::cout << "make sure to include ofxProfiler and define OFX_PROFILER=1" << endl;
|
||||
#endif
|
||||
}
|
||||
void endProfiling(){
|
||||
#if OFX_PROFILER
|
||||
OFX_PROFILER_END_SESSION();
|
||||
|
||||
#ifdef TARGET_EMSCRIPTEN
|
||||
std::ifstream f("results.json");
|
||||
nlohmann::json json = nlohmann::json::parse(f);
|
||||
app->downloadJson("results.json", json);
|
||||
#endif
|
||||
#else
|
||||
std::cout << "not compiled with internal profiling support" << endl;
|
||||
std::cout << "make sure to include ofxProfiler and define OFX_PROFILER=1" << endl;
|
||||
#endif
|
||||
}
|
||||
|
||||
//========================================================================
|
||||
// EMSCRIPTEN BINDING BINDINGS
|
||||
// here we describe classes and functions to bind them to javascript
|
||||
|
||||
EMSCRIPTEN_BINDINGS(name_is_irrelevant){
|
||||
emscripten::value_object <VariableEditor::ArtboardProps>("ArtboardProps")
|
||||
.field("backgroundColor", &VariableEditor::ArtboardProps::backgroundColor)
|
||||
.field("x", &VariableEditor::ArtboardProps::x)
|
||||
.field("y", &VariableEditor::ArtboardProps::y)
|
||||
.field("width", &VariableEditor::ArtboardProps::width)
|
||||
.field("height", &VariableEditor::ArtboardProps::height)
|
||||
.field("zoom", &VariableEditor::ArtboardProps::zoom)
|
||||
.field("pixelDensity", &VariableEditor::ArtboardProps::pixelDensity)
|
||||
;
|
||||
emscripten::value_object <ofxVariableLab::Layer::Props>("Props")
|
||||
.field("x", &ofxVariableLab::Layer::Props::x)
|
||||
.field("y", &ofxVariableLab::Layer::Props::y)
|
||||
.field("width", &ofxVariableLab::Layer::Props::width)
|
||||
.field("height", &ofxVariableLab::Layer::Props::height)
|
||||
.field("rotation", &ofxVariableLab::Layer::Props::rotation)
|
||||
.field("fontSize_px", &ofxVariableLab::Layer::Props::fontSize_px)
|
||||
.field("letterSpacing", &ofxVariableLab::Layer::Props::letterSpacing)
|
||||
.field("lineHeight", &ofxVariableLab::Layer::Props::lineHeight)
|
||||
.field("textAlignment", &ofxVariableLab::Layer::Props::textAlignment)
|
||||
.field("color", &ofxVariableLab::Layer::Props::color)
|
||||
.field("mirror_x", &ofxVariableLab::Layer::Props::mirror_x)
|
||||
.field("mirror_x_distance", &ofxVariableLab::Layer::Props::mirror_x_distance)
|
||||
.field("mirror_y", &ofxVariableLab::Layer::Props::mirror_y)
|
||||
.field("mirror_y_distance", &ofxVariableLab::Layer::Props::mirror_y_distance)
|
||||
.field("mirror_xy", &ofxVariableLab::Layer::Props::mirror_xy)
|
||||
.field("letterDelay", &ofxVariableLab::Layer::Props::letterDelay)
|
||||
.field("transformOrigin", &ofxVariableLab::Layer::Props::transformOrigin)
|
||||
.field("text", &ofxVariableLab::Layer::Props::text)
|
||||
.field("fontPath", &ofxVariableLab::Layer::Props::fontPath)
|
||||
.field("fontVariations", &ofxVariableLab::Layer::Props::fontVariations)
|
||||
.field("letterDelays", &ofxVariableLab::Layer::Props::letterDelays)
|
||||
;
|
||||
emscripten::value_object <ofxVariableLab::FontVariationAxis>("FontVariationAxis")
|
||||
.field("name", &ofxVariableLab::FontVariationAxis::name)
|
||||
.field("minValue", &ofxVariableLab::FontVariationAxis::minValue)
|
||||
.field("maxValue", &ofxVariableLab::FontVariationAxis::maxValue)
|
||||
.field("defaultValue", &ofxVariableLab::FontVariationAxis::defaultValue)
|
||||
;
|
||||
emscripten::value_object <ofxVariableLab::Layer::BoundingBox>("BoundingBox")
|
||||
.field("x", &ofxVariableLab::Layer::BoundingBox::x)
|
||||
.field("y", &ofxVariableLab::Layer::BoundingBox::y)
|
||||
.field("w", &ofxVariableLab::Layer::BoundingBox::w)
|
||||
.field("h", &ofxVariableLab::Layer::BoundingBox::h)
|
||||
;
|
||||
// Register std::array<float, 4> because Props::color is interpreted as such
|
||||
emscripten::value_array <std::array <float, 4> >("array_float_4")
|
||||
.element(emscripten::index <0>())
|
||||
.element(emscripten::index <1>())
|
||||
.element(emscripten::index <2>())
|
||||
.element(emscripten::index <3>())
|
||||
;
|
||||
emscripten::class_ <ofxVariableLab::FontVariation>("FontVariation")
|
||||
.constructor <>()
|
||||
.property("name", &ofxVariableLab::FontVariation::name)
|
||||
.property("value", &ofxVariableLab::FontVariation::value)
|
||||
;
|
||||
emscripten::register_vector <ofxVariableLab::FontVariationAxis>("FontVariationAxes");
|
||||
emscripten::register_vector <ofxVariableLab::FontVariation>("FontVariations");
|
||||
emscripten::register_vector <std::string>("vector<string>");
|
||||
emscripten::register_map <std::string, float>("map<string, float>");
|
||||
|
||||
emscripten::function("addExistingLayer", &addExistingLayer);
|
||||
emscripten::function("addNewLayer", &addNewLayer);
|
||||
emscripten::function("removeLayer", &removeLayer);
|
||||
emscripten::function("getProps", &getProps);
|
||||
emscripten::function("setProps", &setProps);
|
||||
//emscripten::function("listAvailableFontsAndAxes", &listAvailableFontsAndAxes);
|
||||
emscripten::function("listAvailableFonts", &listAvailableFonts);
|
||||
emscripten::function("listVariationAxes", &listVariationAxes);
|
||||
emscripten::function("getArtboardProps", &getArtboardProps);
|
||||
emscripten::function("setArtboardProps", &setArtboardProps);
|
||||
emscripten::function("getBoundingBox", &getBoundingBox);
|
||||
emscripten::function("resetLetterDelay", &resetLetterDelay);
|
||||
emscripten::function("setLayerOrder", &setLayerOrder);
|
||||
emscripten::function("setPlaying", &setPlaying);
|
||||
emscripten::function("setRendering", &setRendering);
|
||||
emscripten::function("getRendering", &getRendering);
|
||||
emscripten::function("setTimeScale", &setTimeScale);
|
||||
emscripten::function("windowResized", &windowResized);
|
||||
emscripten::function("exportFramesAsZip", &exportFramesAsZip);
|
||||
emscripten::function("importProjectAsZip", &importProjectAsZip, emscripten::allow_raw_pointers());
|
||||
emscripten::function("downloadProject", &downloadProject);
|
||||
// debug
|
||||
emscripten::function("setUniform1f", &setUniform1f);
|
||||
emscripten::function("setUniform1i", &setUniform1i);
|
||||
emscripten::function("startProfiling", &startProfiling);
|
||||
emscripten::function("endProfiling", &endProfiling);
|
||||
emscripten::function("debug_getLayers", &debug_getLayers);
|
||||
emscripten::function("debug_downloadFbo", &debug_downloadFbo);
|
||||
}
|
||||
#endif
|
||||
|
||||
//========================================================================
|
||||
// MAIN
|
||||
// well, we have to start somewhere
|
||||
int main(){
|
||||
#ifdef OF_TARGET_OPENGLES
|
||||
ofGLESWindowSettings settings;
|
||||
//settings.setSize(1920, 1080);
|
||||
settings.numSamples = 4;
|
||||
settings.glesVersion = 3;
|
||||
#else
|
||||
ofGLWindowSettings settings;
|
||||
settings.windowMode = OF_WINDOW;
|
||||
settings.setSize(1920, 1080);
|
||||
settings.setPosition(glm::vec2(1920, 0));
|
||||
settings.setGLVersion(3, 2);
|
||||
#endif
|
||||
ofCreateWindow(settings);
|
||||
|
||||
app = make_shared <VariableEditor::ofApp>();
|
||||
// this kicks off the running of my app
|
||||
// can be OF_WINDOW or OF_FULLSCREEN
|
||||
// pass in width and height too:
|
||||
ofRunApp(app);
|
||||
}
|
506
src/ofApp.cpp
Normal file
506
src/ofApp.cpp
Normal file
|
@ -0,0 +1,506 @@
|
|||
#include "ofApp.h"
|
||||
#include "Layer.h"
|
||||
#include "LayerComposition.h"
|
||||
#include "MsdfLayer.h"
|
||||
#include "conversion.h"
|
||||
#include "import-font.h"
|
||||
#include "of3dUtils.h"
|
||||
#include "ofAppRunner.h"
|
||||
#include "ofColor.h"
|
||||
#include "ofEvents.h"
|
||||
#include "ofFileUtils.h"
|
||||
#include "ofGraphics.h"
|
||||
#include "ofGraphicsBaseTypes.h"
|
||||
#include "ofGraphicsConstants.h"
|
||||
#include "ofTexture.h"
|
||||
#include "ofUtils.h"
|
||||
#include "ofWindowSettings.h"
|
||||
#include "ofxProfiler.h"
|
||||
#include <GL/glext.h>
|
||||
#include <filesystem>
|
||||
#ifdef TARGET_EMSCRIPTEN
|
||||
#include <emscripten/em_asm.h>
|
||||
#include <GLES/gl.h>
|
||||
#include <GLES3/gl3.h>
|
||||
#endif
|
||||
#include <memory>
|
||||
|
||||
static int AA = 1;
|
||||
|
||||
namespace VariableEditor {
|
||||
//--------------------------------------------------------------
|
||||
void ofApp::setup(){
|
||||
OFX_PROFILER_FUNCTION();
|
||||
EM_ASM({window.setLoadingTask('setting up rendering', 0)});
|
||||
|
||||
{
|
||||
ofFile sf("appSettings.json");
|
||||
if(sf.exists()){
|
||||
ofJson json = ofLoadJson("appSettings.json");
|
||||
settings.from_json(json, settings);
|
||||
}
|
||||
}
|
||||
|
||||
ofSetFrameRate(30);
|
||||
ofSetVerticalSync(false);
|
||||
|
||||
cam.setVFlip(true); // otherwise everything is weird
|
||||
cam.setPosition(ofGetWidth() / 2, ofGetHeight() / 2, 935.335);
|
||||
//cam.setPosition(0, 0, -1000);
|
||||
cam.lookAt(glm::vec3(cam.getPosition().x, cam.getPosition().y, 0),
|
||||
glm::vec3(0, 1, 0));
|
||||
//cam.disableMouseInput();
|
||||
cam.enableOrtho();
|
||||
|
||||
observer.setupPerspective(true, // vflip
|
||||
60, // fov
|
||||
0, // nearDist
|
||||
10000000, // farDist
|
||||
glm::vec2(0, 0) // lensOffset
|
||||
);
|
||||
EM_ASM({window.setLoadingTask('setting up rendering', 10)});
|
||||
|
||||
ofDisableArbTex();
|
||||
fboSettings.width = ofGetWidth() * AA;
|
||||
fboSettings.height = ofGetHeight() * AA;
|
||||
fboSettings.numSamples = 0;
|
||||
fboSettings.internalformat = GL_RGBA;
|
||||
fboSettings.minFilter = GL_LINEAR_MIPMAP_LINEAR;
|
||||
fboSettings.maxFilter = GL_LINEAR;
|
||||
//fboSettings.textureTarget = GL_TEXTURE_2D_MULTISAMPLE;
|
||||
fbo.allocate(fboSettings);
|
||||
|
||||
fbo.begin();
|
||||
ofClear(ofColor(settings.backgroundColor[0],
|
||||
settings.backgroundColor[1],
|
||||
settings.backgroundColor[2],
|
||||
settings.backgroundColor[3]));
|
||||
fbo.end();
|
||||
|
||||
//fbo.allocate(ofGetWidth() * AA, ofGetHeight() * AA, GL_RGB);
|
||||
|
||||
EM_ASM({window.setLoadingTask('setting up rendering', 30)});
|
||||
layerComposition.setup();
|
||||
EM_ASM({window.setLoadingTask('setting up rendering', 90)});
|
||||
layerComposition.setVFlip(true);
|
||||
|
||||
#ifndef TARGET_OPENGLES
|
||||
{
|
||||
std::string fontPath = "data/fonts/Version-2-var.ttf";
|
||||
ofxVariableLab::LayerType type = ofxVariableLab::LayerType::GPUFONT;
|
||||
ofxVariableLab::Layer::Props props;
|
||||
props.fontPath = fontPath;
|
||||
props.text = "yo, whatever you want, and especially pancakes";
|
||||
props.y = 120;
|
||||
props.x = 95;
|
||||
layerComposition.addLayer(
|
||||
{fontPath, type},
|
||||
props,
|
||||
{{"Weight", 100.0}, {"Weight", 700.0}}
|
||||
);
|
||||
}
|
||||
#endif
|
||||
|
||||
image42.load("42px-01.png");
|
||||
image420.load("420px-01.png");
|
||||
|
||||
int maxSamples;
|
||||
glGetIntegerv(GL_MAX_SAMPLES, &maxSamples);
|
||||
cout << "MAX_SAMPLES: " << ofToString(maxSamples) << endl;
|
||||
|
||||
int maxTextureSize;
|
||||
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
|
||||
cout << "MAX_TEXTURE_SIZE: " << ofToString(maxTextureSize) << endl;
|
||||
if(maxTextureSize < 16384){
|
||||
#ifdef TARGET_EMSCRIPTEN
|
||||
// yeaaaaahh, well this is mostly for firefox on a mac
|
||||
EM_ASM({
|
||||
alert('Your browser / operating system only allows quite small textures, which can lead to glitches.\n\nWe recommend:\nMac OS + Chrome\nWindows + Chrome\nLinux + Chrome / Firefox\n\nPS: If you do not want to use Chrome, Brave is a good Chrome-based browser.');
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
const unsigned char * glVersion = glGetString(GL_VERSION);
|
||||
int glMajor, glMinor;
|
||||
glGetIntegerv(GL_MAJOR_VERSION, &glMajor);
|
||||
glGetIntegerv(GL_MINOR_VERSION, &glMinor);
|
||||
|
||||
cout << "GL_VERSION: " << glVersion
|
||||
<< " | Major(" << ofToString(glMajor) << ")"
|
||||
<< " | Minor(" << ofToString(glMajor) << ")"
|
||||
<< endl;
|
||||
artboard.setup();
|
||||
EM_ASM({window.setLoadingTask('setting up rendering', 100)});
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------
|
||||
void ofApp::update(){
|
||||
OFX_PROFILER_FUNCTION();
|
||||
if(!rendering || renderNextFrame){
|
||||
layerComposition.update();
|
||||
}
|
||||
zipSaver.update();
|
||||
projectZipSaver.update();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------
|
||||
void ofApp::draw(){
|
||||
OFX_PROFILER_FUNCTION();
|
||||
if(!rendering || renderNextFrame){
|
||||
ofCamera & camera = observing ? observer : cam;
|
||||
|
||||
artboard.begin();
|
||||
camera.begin();
|
||||
ofPushMatrix();
|
||||
layerComposition.draw(artboard.getShape().x,
|
||||
artboard.getShape().y);
|
||||
ofPopMatrix();
|
||||
camera.end();
|
||||
artboard.end();
|
||||
|
||||
fbo.begin();
|
||||
ofFloatColor bg(settings.backgroundColor[0],
|
||||
settings.backgroundColor[1],
|
||||
settings.backgroundColor[2],
|
||||
settings.backgroundColor[3]);
|
||||
ofClear(bg);
|
||||
artboard.draw();
|
||||
fbo.end();
|
||||
ofDisableAlphaBlending();
|
||||
ofDisableDepthTest();
|
||||
|
||||
fbo.getTexture().generateMipmap();
|
||||
fbo.draw(0, 0, ofGetWidth(), ofGetHeight());
|
||||
|
||||
ofPushStyle();
|
||||
ofDrawBitmapStringHighlight("fps: " + ofToString(ofGetFrameRate()), 20, ofGetHeight() - 120);
|
||||
//ofDrawBitmapStringHighlight("ortho: " + ofToString(cam.getOrtho()), 20, ofGetHeight() - 200);
|
||||
ofPopStyle();
|
||||
|
||||
ofEnableDepthTest();
|
||||
}
|
||||
if(rendering){
|
||||
//if(currentFrame < guessedFrameCount){
|
||||
int n_zero = log10(guessedFrameCount) + 1;
|
||||
string frame_number_padded = std::string(n_zero, 'x');
|
||||
if(currentFrame > 0){
|
||||
string frame_number = ofToString(currentFrame - 1);
|
||||
frame_number_padded = std::string(n_zero - std::min(n_zero, (int)frame_number.length()), '0') + frame_number;
|
||||
saveFrame(frame_number_padded, artboard.getFbo());
|
||||
recordedFrameNames.push_back(frame_number_padded);
|
||||
}
|
||||
currentFrame++;
|
||||
#ifdef TARGET_EMSCRIPTEN
|
||||
int percent = (theatrePosition / timelineLength_seconds) * 100;
|
||||
EM_ASM_INT({
|
||||
let percent = $0;
|
||||
document.getElementById('export_progress_task').innerHTML = 'rendering';
|
||||
let innerHTML = "|";
|
||||
const niceText = "rendering is the process of generating an image by means of a computer program. ";
|
||||
for(let i = 0; i < 100; i++){
|
||||
if(i < percent){
|
||||
innerHTML += niceText[i % niceText.length];
|
||||
}else{
|
||||
innerHTML += "-";
|
||||
}
|
||||
}
|
||||
innerHTML += "|";
|
||||
let progress = document.getElementById("export_progress");
|
||||
progress.innerHTML = innerHTML;
|
||||
}, percent);
|
||||
double theatrePostPosition = EM_ASM_DOUBLE({
|
||||
window.tp.sheet.sequence.position = $0;
|
||||
return window.tp.sheet.sequence.position;
|
||||
}, theatrePosition);
|
||||
if(theatrePostPosition >= timelineLength_seconds
|
||||
|| theatrePosition > theatrePostPosition){
|
||||
cout << std::fixed
|
||||
<< "------------------------- "
|
||||
<< "frame: " << frame_number_padded << endl
|
||||
<< "theatrePosition: " << ofToString(theatrePosition) << std::endl
|
||||
<< "timelineLEngth|_second: " << ofToString(timelineLength_seconds) << std::endl
|
||||
<< "theatrePostPosition: " << ofToString(theatrePostPosition) << std::endl
|
||||
;
|
||||
EM_ASM({
|
||||
window.renderDone();
|
||||
});
|
||||
theatrePosition = 0;
|
||||
renderNextFrame = 0;
|
||||
rendering = false;
|
||||
}else{
|
||||
theatrePosition += timeScale / 30.0f;
|
||||
renderNextFrame = true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
//}
|
||||
}
|
||||
|
||||
void ofApp::drawGrid(){
|
||||
|
||||
}
|
||||
|
||||
void ofApp::setPlaying(bool playing){
|
||||
this->playing = playing;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------
|
||||
void ofApp::keyPressed(int key){
|
||||
//cout << "pressed " << (char)key << "(" << ofToString(key) << ")" << endl;
|
||||
if(inputPressed.count(ofKey(key)) == 0){
|
||||
inputPressed.insert(ofKey(key));
|
||||
}
|
||||
return;
|
||||
if(key == 's'){
|
||||
#ifdef TARGET_EMSCRIPTEN
|
||||
//atlasImage->save("web_atlasImage.png");
|
||||
for(const auto & a : layerComposition.getAtlasLayerCombos()){
|
||||
if(a.second->getIdentifier().type == ofxVariableLab::LayerType::MSDFGEN){
|
||||
auto combo =
|
||||
static_pointer_cast <ofxVariableLab::MsdfAtlasLayerCombo>(a.second);
|
||||
string imageName = combo->getAtlasImagePath();
|
||||
ofStringReplace(imageName, "/", "_");
|
||||
ofStringReplace(imageName, "data_atlascache_", "");
|
||||
downloadImage(imageName, combo->getAtlasImage());
|
||||
}else if(a.second->getIdentifier().type == ofxVariableLab::LayerType::GPUFONT){
|
||||
auto combo =
|
||||
static_pointer_cast <ofxVariableLab::GPUFontAtlasLayerCombo>(a.second);
|
||||
}
|
||||
}
|
||||
#else
|
||||
for(const auto & a : layerComposition.getAtlasLayerCombos()){
|
||||
if(a.second->getIdentifier().type == ofxVariableLab::LayerType::MSDFGEN){
|
||||
auto combo =
|
||||
static_pointer_cast <ofxVariableLab::MsdfAtlasLayerCombo>(a.second);
|
||||
string imageName = combo->getAtlasImagePath();
|
||||
ofStringReplace(imageName, "/", "_");
|
||||
ofStringReplace(imageName, "data_atlascache_", "");
|
||||
combo->getAtlasImage().save(imageName);
|
||||
}else if(a.second->getIdentifier().type == ofxVariableLab::LayerType::GPUFONT){
|
||||
auto combo =
|
||||
static_pointer_cast <ofxVariableLab::GPUFontAtlasLayerCombo>(a.second);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
if(key == 'a'){
|
||||
#ifdef TARGET_EMSCRIPTEN
|
||||
downloadFboAsImage("web_fboImage.png", fbo);
|
||||
//atlasImage->save("web_atlasImage.png");
|
||||
#else
|
||||
ofPixels pixels;
|
||||
fbo.readToPixels(pixels);
|
||||
ofSaveImage(pixels, "linux64_fboImage.png");
|
||||
#endif
|
||||
}
|
||||
if(key == 'o'){
|
||||
if(cam.getOrtho()){
|
||||
cout << "diable ortho" << endl;
|
||||
cam.disableOrtho();
|
||||
}else{
|
||||
cout << "eable ortho" << endl;
|
||||
cam.enableOrtho();
|
||||
}
|
||||
}
|
||||
if(key == 'p'){
|
||||
observing = !observing;
|
||||
}
|
||||
if(key == 'c'){
|
||||
cout << "observer to cam" << endl;
|
||||
observer.setGlobalOrientation(cam.getGlobalOrientation());
|
||||
observer.setGlobalPosition(cam.getGlobalPosition());
|
||||
observer.setScale(cam.getGlobalScale());
|
||||
observer.setFov(cam.getFov());
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------
|
||||
void ofApp::keyReleased(int key){
|
||||
//cout << "released " << (char)key << "(" << ofToString(key) << ")" << endl;
|
||||
if(inputPressed.count(ofKey(key)) > 0){
|
||||
inputPressed.erase(ofKey(key));
|
||||
}
|
||||
#ifdef OFX_PROFILER
|
||||
//if(key == 'o'){
|
||||
//OFX_PROFILER_BEGIN_SESSION("profiling", "results.json");
|
||||
//}
|
||||
//if(key == 'p'){
|
||||
//OFX_PROFILER_END_SESSION();
|
||||
|
||||
//#ifdef TARGET_EMSCRIPTEN
|
||||
//std::ifstream f("results.json");
|
||||
//nlohmann::json json = nlohmann::json::parse(f);
|
||||
//downloadJson("results.json", json);
|
||||
//#endif
|
||||
//cout << json.dump() << endl;
|
||||
//}
|
||||
//if(key == 'r'){
|
||||
//rendering = !rendering;
|
||||
//}
|
||||
#endif
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------
|
||||
void ofApp::mouseMoved(int x, int y){
|
||||
//cout << "mouse moved" << endl;
|
||||
//if(inputPressed.count(OF_MOUSE_BUTTON_MIDDLE) > 0
|
||||
//|| (inputPressed.count(OF_MOUSE_BUTTON_LEFT) > 0 && inputPressed.count(OF_KEY_CONTROL) > 0)){
|
||||
//artboard.setPosition(x, y, 0);
|
||||
//}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------
|
||||
void ofApp::mouseDragged(int x, int y, int button){
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------
|
||||
void ofApp::mousePressed(int x, int y, int button){
|
||||
//cout << "mouse pressed " << button << endl;
|
||||
if(inputPressed.count(ofKey(button)) == 0){
|
||||
inputPressed.insert(ofKey(button));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------
|
||||
void ofApp::mouseReleased(int x, int y, int button){
|
||||
//cout << "mouse released " << button << endl;
|
||||
if(inputPressed.count(ofKey(button)) == 0){
|
||||
inputPressed.erase(ofKey(button));
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------
|
||||
void ofApp::mouseEntered(int x, int y){
|
||||
//cout << "mouse entered" << endl;
|
||||
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------
|
||||
void ofApp::mouseExited(int x, int y){
|
||||
//cout << "mouse exited" << endl;
|
||||
|
||||
}
|
||||
|
||||
void ofApp::mouseScrolled(ofMouseEventArgs & mouse){
|
||||
this->mouseScrolled(mouse.x, mouse.y, mouse.scrollX, mouse.scrollY);
|
||||
}
|
||||
|
||||
void ofApp::mouseScrolled(int x, int y, float scrollX, float scrollY){
|
||||
//cout << "scroll "
|
||||
//<< "x: " << ofToString(x) << " "
|
||||
//<< "y: " << ofToString(y) << " "
|
||||
//<< "scrollX: " << ofToString(scrollX) << " "
|
||||
//<< "scrollY: " << ofToString(scrollY) << " "
|
||||
//;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------
|
||||
void ofApp::windowResized(int w, int h){
|
||||
cout << "window resized " << ofToString(w) << "x" << ofToString(h) << endl;
|
||||
ofSetWindowShape(w, h);
|
||||
fboSettings.width = w * AA;
|
||||
fboSettings.height = h * AA;
|
||||
fboSettings.numSamples = 0;
|
||||
fboSettings.internalformat = GL_RGBA;
|
||||
fboSettings.minFilter = GL_LINEAR_MIPMAP_LINEAR;
|
||||
fboSettings.maxFilter = GL_LINEAR;
|
||||
//fboSettings.textureTarget = GL_TEXTURE_2D_MULTISAMPLE;
|
||||
ofFbo newFbo;
|
||||
newFbo.allocate(fboSettings);
|
||||
|
||||
newFbo.begin();
|
||||
ofClear(ofColor(settings.backgroundColor[0],
|
||||
settings.backgroundColor[1],
|
||||
settings.backgroundColor[2],
|
||||
settings.backgroundColor[3]));
|
||||
newFbo.end();
|
||||
fbo = std::move(newFbo);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------
|
||||
void ofApp::gotMessage(ofMessage msg){
|
||||
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------
|
||||
void ofApp::dragEvent(ofDragInfo dragInfo){
|
||||
|
||||
}
|
||||
|
||||
void ofApp::saveFrame(const string & filename,
|
||||
const ofFbo & _fbo){
|
||||
ofImage image;
|
||||
image.setUseTexture(false);
|
||||
image.allocate(_fbo.getWidth(),
|
||||
_fbo.getHeight(),
|
||||
OF_IMAGE_COLOR_ALPHA);
|
||||
_fbo.readToPixels(image.getPixels());
|
||||
image.save(settings.tmpExportDir + "/frames/" + filename + ".png");
|
||||
}
|
||||
|
||||
#ifdef TARGET_EMSCRIPTEN
|
||||
void ofApp::downloadImage(const string & filename,
|
||||
const ofImage & image){
|
||||
downloadPixelsAsImage(filename, image.getPixels());
|
||||
}
|
||||
void ofApp::downloadFramesAsZip(const string & projectName){
|
||||
if(!zipSaver.isThreadRunning()){
|
||||
cout << "ofApp::downloadFramesAsZip" << endl;
|
||||
zipSaver.setup(projectName,
|
||||
settings.tmpExportDir + "/frames",
|
||||
recordedFrameNames
|
||||
);
|
||||
zipSaver.startThread();
|
||||
}
|
||||
}
|
||||
|
||||
void ofApp::downloadFboAsImage(const string & filename,
|
||||
const ofFbo & _fbo){
|
||||
ofPixels pixels;
|
||||
_fbo.readToPixels(pixels);
|
||||
downloadPixelsAsImage(filename, pixels);
|
||||
}
|
||||
|
||||
void ofApp::downloadFrame(const string & filename){
|
||||
cout << "downloadFrame " << filename << endl;
|
||||
ofImage image;
|
||||
image.load(settings.tmpExportDir + "/frames/" + filename + ".png");
|
||||
downloadImage(filename,
|
||||
image);
|
||||
}
|
||||
|
||||
void ofApp::downloadPixelsAsImage(const string & filename,
|
||||
const ofPixels & pixels){
|
||||
ofBuffer buffer;
|
||||
ofSaveImage(pixels, buffer, OF_IMAGE_FORMAT_PNG);
|
||||
emscripten_browser_file::download(filename.c_str(),
|
||||
"image/png",
|
||||
buffer.getData(),
|
||||
buffer.size());
|
||||
}
|
||||
|
||||
void ofApp::downloadJson(const string & filename,
|
||||
const nlohmann::json & json){
|
||||
string jsonString = json.dump();
|
||||
emscripten_browser_file::download(filename.c_str(),
|
||||
"application/json",
|
||||
jsonString.data(),
|
||||
jsonString.size());
|
||||
}
|
||||
|
||||
void ofApp::downloadProject(const string & projectName,
|
||||
const string & projectJsonString){
|
||||
if(!projectZipSaver.isThreadRunning()){
|
||||
projectZipSaver.setup(projectName,
|
||||
projectJsonString);
|
||||
projectZipSaver.startThread();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
void ofApp::exit(){
|
||||
zipSaver.stopThread();
|
||||
}
|
||||
|
||||
}
|
343
src/ofApp.h
Normal file
343
src/ofApp.h
Normal file
|
@ -0,0 +1,343 @@
|
|||
#pragma once
|
||||
|
||||
#include "Zip.h"
|
||||
#include "Atlas.h"
|
||||
#include "conversion.h"
|
||||
#include "Artboard.h"
|
||||
#include "ofEasyCam.h"
|
||||
#include "ofMain.h"
|
||||
#include "ofQuaternion.h"
|
||||
#include "ofTrueTypeFont.h"
|
||||
#include "ofxVariableLab.h"
|
||||
#include "ofxProfiler.h"
|
||||
#include <unordered_map>
|
||||
#ifdef TARGET_EMSCRIPTEN
|
||||
#include <emscripten/em_macros.h>
|
||||
#include <emscripten/bind.h>
|
||||
#include <emscripten.h>
|
||||
#include <emscripten-browser-file/emscripten_browser_file.h>
|
||||
#endif
|
||||
|
||||
using namespace msdfgen;
|
||||
|
||||
// TODO: better antialias
|
||||
// possibly just draw a bigger fbo?
|
||||
// or do it properly:
|
||||
// https://github.com/emscripten-core/emscripten/issues/7898
|
||||
//
|
||||
// TODO: fix linux build
|
||||
|
||||
namespace VariableEditor {
|
||||
|
||||
struct AppSettings {
|
||||
std::array <float, 4> backgroundColor = {212 / 255.0,
|
||||
212 / 255.0,
|
||||
212 / 255.0,
|
||||
1}; // check data/appSettings.json
|
||||
string tmpExportDir = "data/export";
|
||||
string tmpImportDir = "data/import";
|
||||
|
||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(AppSettings,
|
||||
backgroundColor,
|
||||
tmpExportDir);
|
||||
|
||||
};
|
||||
|
||||
class ZipProjectSaver : public ofThread {
|
||||
public:
|
||||
void setup(string projectName,
|
||||
string projectJsonString){
|
||||
this->projectName = projectName;
|
||||
this->projectJsonString = projectJsonString;
|
||||
this->userFontsPath = "/idbfs/fonts";
|
||||
this->timestamp = ofGetTimestampString();
|
||||
this->filename = projectName + "_project_" + timestamp + ".zip";
|
||||
}
|
||||
|
||||
void update(){
|
||||
if(freshDownload.load()){
|
||||
emscripten_browser_file::download(filename.c_str(),
|
||||
"application/zip",
|
||||
buffer,
|
||||
buffer_size);
|
||||
freshDownload.store(false);
|
||||
}
|
||||
}
|
||||
|
||||
void threadedFunction(){
|
||||
Zip zip(filename.c_str());
|
||||
ofDisableDataPath();
|
||||
|
||||
{
|
||||
char buffy[projectJsonString.length()];
|
||||
projectJsonString.copy(buffy, projectJsonString.length());
|
||||
zip.addBuffer("project.json", buffy, projectJsonString.length());
|
||||
}
|
||||
|
||||
ofDirectory userFonts(userFontsPath);
|
||||
userFonts.sort();
|
||||
userFonts.allowExt("ttf");
|
||||
userFonts.allowExt("otf");
|
||||
userFonts.listDir();
|
||||
for(int i = 0; i < userFonts.size(); i++){
|
||||
std::string fontFilename = userFonts.getName(i);
|
||||
std::string fontFilepath = userFontsPath + "/" + fontFilename;
|
||||
ofFile file = userFonts.getFile(i);
|
||||
ofStringReplace(fontFilepath, "/idbfs/", "idbfs/");
|
||||
if(of::filesystem::exists(fontFilepath)){
|
||||
//cout << "huuurrayy " << fontFilepath << " exists" << endl;
|
||||
}else{
|
||||
cout << "ofApp::downloadProject() trying to load " << fontFilepath << " but it does not exist." << endl;
|
||||
}
|
||||
file.open(fontFilepath);
|
||||
ofBuffer buffy = file.readToBuffer();
|
||||
zip.addBuffer(fontFilename, buffy.getData(), buffy.size());
|
||||
}
|
||||
buffer = NULL;
|
||||
buffer_size = 0;
|
||||
zip.getOutputBuffer(&buffer, buffer_size);
|
||||
zip.close();
|
||||
ofEnableDataPath();
|
||||
freshDownload.store(true);
|
||||
}
|
||||
|
||||
void exit(){
|
||||
free(buffer);
|
||||
}
|
||||
string projectName;
|
||||
string projectJsonString;
|
||||
string userFontsPath;
|
||||
string timestamp;
|
||||
string filename;
|
||||
char * buffer;
|
||||
size_t buffer_size;
|
||||
std::atomic <bool> freshDownload{false};
|
||||
std::atomic <int> percent{0};
|
||||
|
||||
};
|
||||
|
||||
class ZipSaver : public ofThread {
|
||||
public:
|
||||
void setup(string projectName,
|
||||
string framePath,
|
||||
vector <string> recordedFrameNames){
|
||||
this->projectName = projectName;
|
||||
this->framePath = framePath;
|
||||
this->recordedFrameNames = recordedFrameNames;
|
||||
this->timestamp = ofGetTimestampString();
|
||||
this->filename = projectName + "_frames_" + timestamp + ".zip";
|
||||
}
|
||||
|
||||
void update(){
|
||||
if(freshDownload.load()){
|
||||
EM_ASM({
|
||||
document.getElementById('export_progress_task').innerHTML = 'rendering';
|
||||
let innerHTML = "|";
|
||||
for(let i = 0; i < 100; i++){
|
||||
innerHTML += "-";
|
||||
}
|
||||
innerHTML += "|";
|
||||
let progress = document.getElementById("export_progress");
|
||||
progress.innerHTML = innerHTML;
|
||||
let progress_task = document.getElementById("export_progress_task");
|
||||
progress_task.innerHTML = "idle";
|
||||
});
|
||||
emscripten_browser_file::download(filename.c_str(),
|
||||
"application/zip",
|
||||
buffer,
|
||||
buffer_size);
|
||||
freshDownload.store(false);
|
||||
}else if(isThreadRunning()){
|
||||
setProgress(percent.load());
|
||||
}
|
||||
}
|
||||
|
||||
void setProgress(int percent){
|
||||
EM_ASM_INT({
|
||||
let percent = $0;
|
||||
document.getElementById('export_progress_task').innerHTML = 'rendering';
|
||||
let innerHTML = "|";
|
||||
const niceText = "zip ";
|
||||
for(let i = 0; i < 100; i++){
|
||||
if(i < percent){
|
||||
innerHTML += niceText[i % niceText.length];
|
||||
}else{
|
||||
innerHTML += "-";
|
||||
}
|
||||
}
|
||||
innerHTML += "|";
|
||||
let progress = document.getElementById("export_progress");
|
||||
progress.innerHTML = innerHTML;
|
||||
let progress_task = document.getElementById("export_progress_task");
|
||||
progress_task.innerHTML = "creating zip file";
|
||||
}, percent);
|
||||
}
|
||||
|
||||
void threadedFunction(){
|
||||
Zip zip(filename.c_str());
|
||||
ofDisableDataPath();
|
||||
int total = recordedFrameNames.size();
|
||||
int i = 0;
|
||||
for(const std::string & f: recordedFrameNames){
|
||||
std::string filepath = framePath + "/" + f + ".png";
|
||||
if(of::filesystem::exists(filepath)){
|
||||
//cout << "huuurrayy " << filepath << " exists" << endl;
|
||||
}else{
|
||||
cout << "ofApp::downloadFramesAsZip() trying to load " << filepath << " but it does not exist." << endl;
|
||||
}
|
||||
ofImage image;
|
||||
image.setUseTexture(false);
|
||||
image.load(filepath);
|
||||
ofBuffer buffer;
|
||||
ofSaveImage(image.getPixels(), buffer, OF_IMAGE_FORMAT_PNG);
|
||||
zip.addBuffer(f + ".png", buffer.getData(), buffer.size());
|
||||
percent.store((float(i) / float(total)) * 100.0f);
|
||||
i++;
|
||||
}
|
||||
buffer = NULL;
|
||||
buffer_size = 0;
|
||||
zip.getOutputBuffer(&buffer, buffer_size);
|
||||
zip.close();
|
||||
ofEnableDataPath();
|
||||
freshDownload.store(true);
|
||||
}
|
||||
|
||||
void exit(){
|
||||
free(buffer);
|
||||
}
|
||||
string projectName;
|
||||
string framePath;
|
||||
string timestamp;
|
||||
string filename;
|
||||
std::vector <string> recordedFrameNames;
|
||||
char * buffer;
|
||||
size_t buffer_size;
|
||||
std::atomic <bool> freshDownload{false};
|
||||
std::atomic <int> percent{0};
|
||||
|
||||
};
|
||||
|
||||
class ZipUnpacker : public ofThread {
|
||||
public:
|
||||
void setup(){
|
||||
this->timestamp = ofGetTimestampString();
|
||||
}
|
||||
|
||||
void update(){
|
||||
if(freshUpload.load()){
|
||||
freshUpload.store(false);
|
||||
}else if(isThreadRunning()){
|
||||
//setProgress(percent.load());
|
||||
}
|
||||
}
|
||||
|
||||
void setProgress(int percent){
|
||||
EM_ASM_INT({
|
||||
let percent = $0;
|
||||
document.getElementById('export_progress_task').innerHTML = 'uploading and unpacking';
|
||||
let innerHTML = "|";
|
||||
const niceText = "zip ";
|
||||
for(let i = 0; i < 100; i++){
|
||||
if(i < percent){
|
||||
innerHTML += niceText[i % niceText.length];
|
||||
}else{
|
||||
innerHTML += "-";
|
||||
}
|
||||
}
|
||||
innerHTML += "|";
|
||||
let progress = document.getElementById("import_progress");
|
||||
progress.innerHTML = innerHTML;
|
||||
let progress_task = document.getElementById("import_progress_task");
|
||||
progress_task.innerHTML = "creating zip file";
|
||||
}, percent);
|
||||
}
|
||||
|
||||
void threadedFunction(){
|
||||
ofDisableDataPath();
|
||||
}
|
||||
|
||||
void exit(){
|
||||
free(buffer);
|
||||
}
|
||||
string timestamp;
|
||||
char * buffer;
|
||||
size_t buffer_size;
|
||||
std::atomic <bool> freshUpload{false};
|
||||
std::atomic <int> percent{0};
|
||||
|
||||
};
|
||||
|
||||
class ofApp : public ofBaseApp {
|
||||
public:
|
||||
|
||||
void setup() override;
|
||||
void update() override;
|
||||
void draw() override;
|
||||
|
||||
void drawGrid();
|
||||
void setPlaying(bool playing);
|
||||
|
||||
void keyPressed(int key) override;
|
||||
void keyReleased(int key) override;
|
||||
void mouseMoved(int x, int y) override;
|
||||
void mouseDragged(int x, int y, int button) override;
|
||||
void mousePressed(int x, int y, int button) override;
|
||||
void mouseReleased(int x, int y, int button) override;
|
||||
void mouseEntered(int x, int y) override;
|
||||
void mouseExited(int x, int y) override;
|
||||
void mouseScrolled(ofMouseEventArgs & mouse) override;
|
||||
void mouseScrolled(int x, int y, float scrollX, float scrollY) override;
|
||||
void windowResized(int w, int h) override;
|
||||
void dragEvent(ofDragInfo dragInfo) override;
|
||||
void gotMessage(ofMessage msg) override;
|
||||
void saveFrame(const string & filename,
|
||||
const ofFbo & _fbo);
|
||||
#ifdef TARGET_EMSCRIPTEN
|
||||
void downloadFrame(const string & filename);
|
||||
void downloadFramesAsZip(const string & projectName = "project");
|
||||
static void downloadImage(const string & filename,
|
||||
const ofImage & image);
|
||||
static void downloadFboAsImage(const string & filename,
|
||||
const ofFbo & _fbo);
|
||||
static void downloadPixelsAsImage(const string & filename,
|
||||
const ofPixels & image);
|
||||
static void downloadJson(const string & filename,
|
||||
const nlohmann::json & json);
|
||||
void downloadProject(const string & projectName,
|
||||
const string & projectJsonString);
|
||||
#endif
|
||||
void exit() override;
|
||||
|
||||
ofxVariableLab::LayerComposition layerComposition;
|
||||
|
||||
ofCamera cam;
|
||||
ofFboSettings fboSettings;
|
||||
ofFbo fbo;
|
||||
ofEasyCam observer;
|
||||
bool observing = false;
|
||||
bool playing = true;
|
||||
bool rendering = false;
|
||||
bool renderNextFrame = false;
|
||||
int guessedFrameCount = 30;
|
||||
double timelineLength_seconds = 0;
|
||||
int currentFrame = 0;
|
||||
double theatrePosition = 0;
|
||||
double timeScale = 1.0;
|
||||
|
||||
ofImage image42;
|
||||
ofImage image420;
|
||||
|
||||
AppSettings settings;
|
||||
|
||||
ofTrueTypeFont ttf;
|
||||
Artboard artboard;
|
||||
|
||||
std::set <ofKey> inputPressed;
|
||||
|
||||
// EXPORTER
|
||||
std::vector <std::string> recordedFrameNames;
|
||||
ZipSaver zipSaver;
|
||||
ZipProjectSaver projectZipSaver;
|
||||
};
|
||||
|
||||
}
|
10130
src/zip/miniz.h
Normal file
10130
src/zip/miniz.h
Normal file
File diff suppressed because it is too large
Load diff
1793
src/zip/zip.c
Normal file
1793
src/zip/zip.c
Normal file
File diff suppressed because it is too large
Load diff
466
src/zip/zip.h
Normal file
466
src/zip/zip.h
Normal file
|
@ -0,0 +1,466 @@
|
|||
/*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* source: https://github.com/kuba--/zip
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef ZIP_H
|
||||
#define ZIP_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#ifndef ZIP_SHARED
|
||||
#define ZIP_EXPORT
|
||||
#else
|
||||
#ifdef _WIN32
|
||||
#ifdef ZIP_BUILD_SHARED
|
||||
#define ZIP_EXPORT __declspec(dllexport)
|
||||
#else
|
||||
#define ZIP_EXPORT __declspec(dllimport)
|
||||
#endif
|
||||
#else
|
||||
#define ZIP_EXPORT __attribute__((visibility("default")))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if !defined(_POSIX_C_SOURCE) && defined(_MSC_VER)
|
||||
// 64-bit Windows is the only mainstream platform
|
||||
// where sizeof(long) != sizeof(void*)
|
||||
#ifdef _WIN64
|
||||
typedef long long ssize_t; /* byte count or error */
|
||||
#else
|
||||
typedef long ssize_t; /* byte count or error */
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @mainpage
|
||||
*
|
||||
* Documenation for @ref zip.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup zip
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default zip compression level.
|
||||
*/
|
||||
#define ZIP_DEFAULT_COMPRESSION_LEVEL 6
|
||||
|
||||
/**
|
||||
* Error codes
|
||||
*/
|
||||
#define ZIP_ENOINIT -1 // not initialized
|
||||
#define ZIP_EINVENTNAME -2 // invalid entry name
|
||||
#define ZIP_ENOENT -3 // entry not found
|
||||
#define ZIP_EINVMODE -4 // invalid zip mode
|
||||
#define ZIP_EINVLVL -5 // invalid compression level
|
||||
#define ZIP_ENOSUP64 -6 // no zip 64 support
|
||||
#define ZIP_EMEMSET -7 // memset error
|
||||
#define ZIP_EWRTENT -8 // cannot write data to entry
|
||||
#define ZIP_ETDEFLINIT -9 // cannot initialize tdefl compressor
|
||||
#define ZIP_EINVIDX -10 // invalid index
|
||||
#define ZIP_ENOHDR -11 // header not found
|
||||
#define ZIP_ETDEFLBUF -12 // cannot flush tdefl buffer
|
||||
#define ZIP_ECRTHDR -13 // cannot create entry header
|
||||
#define ZIP_EWRTHDR -14 // cannot write entry header
|
||||
#define ZIP_EWRTDIR -15 // cannot write to central dir
|
||||
#define ZIP_EOPNFILE -16 // cannot open file
|
||||
#define ZIP_EINVENTTYPE -17 // invalid entry type
|
||||
#define ZIP_EMEMNOALLOC -18 // extracting data using no memory allocation
|
||||
#define ZIP_ENOFILE -19 // file not found
|
||||
#define ZIP_ENOPERM -20 // no permission
|
||||
#define ZIP_EOOMEM -21 // out of memory
|
||||
#define ZIP_EINVZIPNAME -22 // invalid zip archive name
|
||||
#define ZIP_EMKDIR -23 // make dir error
|
||||
#define ZIP_ESYMLINK -24 // symlink error
|
||||
#define ZIP_ECLSZIP -25 // close archive error
|
||||
#define ZIP_ECAPSIZE -26 // capacity size too small
|
||||
#define ZIP_EFSEEK -27 // fseek error
|
||||
#define ZIP_EFREAD -28 // fread error
|
||||
#define ZIP_EFWRITE -29 // fwrite error
|
||||
|
||||
/**
|
||||
* Looks up the error message string coresponding to an error number.
|
||||
* @param errnum error number
|
||||
* @return error message string coresponding to errnum or NULL if error is not
|
||||
* found.
|
||||
*/
|
||||
extern ZIP_EXPORT const char *zip_strerror(int errnum);
|
||||
|
||||
/**
|
||||
* @struct zip_t
|
||||
*
|
||||
* This data structure is used throughout the library to represent zip archive -
|
||||
* forward declaration.
|
||||
*/
|
||||
struct zip_t;
|
||||
|
||||
/**
|
||||
* Opens zip archive with compression level using the given mode.
|
||||
*
|
||||
* @param zipname zip archive file name.
|
||||
* @param level compression level (0-9 are the standard zlib-style levels).
|
||||
* @param mode file access mode.
|
||||
* - 'r': opens a file for reading/extracting (the file must exists).
|
||||
* - 'w': creates an empty file for writing.
|
||||
* - 'a': appends to an existing archive.
|
||||
*
|
||||
* @return the zip archive handler or NULL on error
|
||||
*/
|
||||
extern ZIP_EXPORT struct zip_t *zip_open(const char *zipname, int level,
|
||||
char mode);
|
||||
|
||||
/**
|
||||
* Closes the zip archive, releases resources - always finalize.
|
||||
*
|
||||
* @param zip zip archive handler.
|
||||
*/
|
||||
extern ZIP_EXPORT void zip_close(struct zip_t *zip);
|
||||
|
||||
/**
|
||||
* Determines if the archive has a zip64 end of central directory headers.
|
||||
*
|
||||
* @param zip zip archive handler.
|
||||
*
|
||||
* @return the return code - 1 (true), 0 (false), negative number (< 0) on
|
||||
* error.
|
||||
*/
|
||||
extern ZIP_EXPORT int zip_is64(struct zip_t *zip);
|
||||
|
||||
/**
|
||||
* Opens an entry by name in the zip archive.
|
||||
*
|
||||
* For zip archive opened in 'w' or 'a' mode the function will append
|
||||
* a new entry. In readonly mode the function tries to locate the entry
|
||||
* in global dictionary.
|
||||
*
|
||||
* @param zip zip archive handler.
|
||||
* @param entryname an entry name in local dictionary.
|
||||
*
|
||||
* @return the return code - 0 on success, negative number (< 0) on error.
|
||||
*/
|
||||
extern ZIP_EXPORT int zip_entry_open(struct zip_t *zip, const char *entryname);
|
||||
|
||||
/**
|
||||
* Opens an entry by name in the zip archive.
|
||||
*
|
||||
* For zip archive opened in 'w' or 'a' mode the function will append
|
||||
* a new entry. In readonly mode the function tries to locate the entry
|
||||
* in global dictionary (case sensitive).
|
||||
*
|
||||
* @param zip zip archive handler.
|
||||
* @param entryname an entry name in local dictionary (case sensitive).
|
||||
*
|
||||
* @return the return code - 0 on success, negative number (< 0) on error.
|
||||
*/
|
||||
extern ZIP_EXPORT int zip_entry_opencasesensitive(struct zip_t *zip,
|
||||
const char *entryname);
|
||||
|
||||
/**
|
||||
* Opens a new entry by index in the zip archive.
|
||||
*
|
||||
* This function is only valid if zip archive was opened in 'r' (readonly) mode.
|
||||
*
|
||||
* @param zip zip archive handler.
|
||||
* @param index index in local dictionary.
|
||||
*
|
||||
* @return the return code - 0 on success, negative number (< 0) on error.
|
||||
*/
|
||||
extern ZIP_EXPORT int zip_entry_openbyindex(struct zip_t *zip, size_t index);
|
||||
|
||||
/**
|
||||
* Closes a zip entry, flushes buffer and releases resources.
|
||||
*
|
||||
* @param zip zip archive handler.
|
||||
*
|
||||
* @return the return code - 0 on success, negative number (< 0) on error.
|
||||
*/
|
||||
extern ZIP_EXPORT int zip_entry_close(struct zip_t *zip);
|
||||
|
||||
/**
|
||||
* Returns a local name of the current zip entry.
|
||||
*
|
||||
* The main difference between user's entry name and local entry name
|
||||
* is optional relative path.
|
||||
* Following .ZIP File Format Specification - the path stored MUST not contain
|
||||
* a drive or device letter, or a leading slash.
|
||||
* All slashes MUST be forward slashes '/' as opposed to backwards slashes '\'
|
||||
* for compatibility with Amiga and UNIX file systems etc.
|
||||
*
|
||||
* @param zip: zip archive handler.
|
||||
*
|
||||
* @return the pointer to the current zip entry name, or NULL on error.
|
||||
*/
|
||||
extern ZIP_EXPORT const char *zip_entry_name(struct zip_t *zip);
|
||||
|
||||
/**
|
||||
* Returns an index of the current zip entry.
|
||||
*
|
||||
* @param zip zip archive handler.
|
||||
*
|
||||
* @return the index on success, negative number (< 0) on error.
|
||||
*/
|
||||
extern ZIP_EXPORT ssize_t zip_entry_index(struct zip_t *zip);
|
||||
|
||||
/**
|
||||
* Determines if the current zip entry is a directory entry.
|
||||
*
|
||||
* @param zip zip archive handler.
|
||||
*
|
||||
* @return the return code - 1 (true), 0 (false), negative number (< 0) on
|
||||
* error.
|
||||
*/
|
||||
extern ZIP_EXPORT int zip_entry_isdir(struct zip_t *zip);
|
||||
|
||||
/**
|
||||
* Returns the uncompressed size of the current zip entry.
|
||||
* Alias for zip_entry_uncomp_size (for backward compatibility).
|
||||
*
|
||||
* @param zip zip archive handler.
|
||||
*
|
||||
* @return the uncompressed size in bytes.
|
||||
*/
|
||||
extern ZIP_EXPORT unsigned long long zip_entry_size(struct zip_t *zip);
|
||||
|
||||
/**
|
||||
* Returns the uncompressed size of the current zip entry.
|
||||
*
|
||||
* @param zip zip archive handler.
|
||||
*
|
||||
* @return the uncompressed size in bytes.
|
||||
*/
|
||||
extern ZIP_EXPORT unsigned long long zip_entry_uncomp_size(struct zip_t *zip);
|
||||
|
||||
/**
|
||||
* Returns the compressed size of the current zip entry.
|
||||
*
|
||||
* @param zip zip archive handler.
|
||||
*
|
||||
* @return the compressed size in bytes.
|
||||
*/
|
||||
extern ZIP_EXPORT unsigned long long zip_entry_comp_size(struct zip_t *zip);
|
||||
|
||||
/**
|
||||
* Returns CRC-32 checksum of the current zip entry.
|
||||
*
|
||||
* @param zip zip archive handler.
|
||||
*
|
||||
* @return the CRC-32 checksum.
|
||||
*/
|
||||
extern ZIP_EXPORT unsigned int zip_entry_crc32(struct zip_t *zip);
|
||||
|
||||
/**
|
||||
* Compresses an input buffer for the current zip entry.
|
||||
*
|
||||
* @param zip zip archive handler.
|
||||
* @param buf input buffer.
|
||||
* @param bufsize input buffer size (in bytes).
|
||||
*
|
||||
* @return the return code - 0 on success, negative number (< 0) on error.
|
||||
*/
|
||||
extern ZIP_EXPORT int zip_entry_write(struct zip_t *zip, const void *buf,
|
||||
size_t bufsize);
|
||||
|
||||
/**
|
||||
* Compresses a file for the current zip entry.
|
||||
*
|
||||
* @param zip zip archive handler.
|
||||
* @param filename input file.
|
||||
*
|
||||
* @return the return code - 0 on success, negative number (< 0) on error.
|
||||
*/
|
||||
extern ZIP_EXPORT int zip_entry_fwrite(struct zip_t *zip, const char *filename);
|
||||
|
||||
/**
|
||||
* Extracts the current zip entry into output buffer.
|
||||
*
|
||||
* The function allocates sufficient memory for a output buffer.
|
||||
*
|
||||
* @param zip zip archive handler.
|
||||
* @param buf output buffer.
|
||||
* @param bufsize output buffer size (in bytes).
|
||||
*
|
||||
* @note remember to release memory allocated for a output buffer.
|
||||
* for large entries, please take a look at zip_entry_extract function.
|
||||
*
|
||||
* @return the return code - the number of bytes actually read on success.
|
||||
* Otherwise a negative number (< 0) on error.
|
||||
*/
|
||||
extern ZIP_EXPORT ssize_t zip_entry_read(struct zip_t *zip, void **buf,
|
||||
size_t *bufsize);
|
||||
|
||||
/**
|
||||
* Extracts the current zip entry into a memory buffer using no memory
|
||||
* allocation.
|
||||
*
|
||||
* @param zip zip archive handler.
|
||||
* @param buf preallocated output buffer.
|
||||
* @param bufsize output buffer size (in bytes).
|
||||
*
|
||||
* @note ensure supplied output buffer is large enough.
|
||||
* zip_entry_size function (returns uncompressed size for the current
|
||||
* entry) can be handy to estimate how big buffer is needed.
|
||||
* For large entries, please take a look at zip_entry_extract function.
|
||||
*
|
||||
* @return the return code - the number of bytes actually read on success.
|
||||
* Otherwise a negative number (< 0) on error (e.g. bufsize is not large
|
||||
* enough).
|
||||
*/
|
||||
extern ZIP_EXPORT ssize_t zip_entry_noallocread(struct zip_t *zip, void *buf,
|
||||
size_t bufsize);
|
||||
|
||||
/**
|
||||
* Extracts the current zip entry into output file.
|
||||
*
|
||||
* @param zip zip archive handler.
|
||||
* @param filename output file.
|
||||
*
|
||||
* @return the return code - 0 on success, negative number (< 0) on error.
|
||||
*/
|
||||
extern ZIP_EXPORT int zip_entry_fread(struct zip_t *zip, const char *filename);
|
||||
|
||||
/**
|
||||
* Extracts the current zip entry using a callback function (on_extract).
|
||||
*
|
||||
* @param zip zip archive handler.
|
||||
* @param on_extract callback function.
|
||||
* @param arg opaque pointer (optional argument, which you can pass to the
|
||||
* on_extract callback)
|
||||
*
|
||||
* @return the return code - 0 on success, negative number (< 0) on error.
|
||||
*/
|
||||
extern ZIP_EXPORT int
|
||||
zip_entry_extract(struct zip_t *zip,
|
||||
size_t (*on_extract)(void *arg, uint64_t offset,
|
||||
const void *data, size_t size),
|
||||
void *arg);
|
||||
|
||||
/**
|
||||
* Returns the number of all entries (files and directories) in the zip archive.
|
||||
*
|
||||
* @param zip zip archive handler.
|
||||
*
|
||||
* @return the return code - the number of entries on success, negative number
|
||||
* (< 0) on error.
|
||||
*/
|
||||
extern ZIP_EXPORT ssize_t zip_entries_total(struct zip_t *zip);
|
||||
|
||||
/**
|
||||
* Deletes zip archive entries.
|
||||
*
|
||||
* @param zip zip archive handler.
|
||||
* @param entries array of zip archive entries to be deleted.
|
||||
* @param len the number of entries to be deleted.
|
||||
* @return the number of deleted entries, or negative number (< 0) on error.
|
||||
*/
|
||||
extern ZIP_EXPORT ssize_t zip_entries_delete(struct zip_t *zip,
|
||||
char *const entries[], size_t len);
|
||||
|
||||
/**
|
||||
* Extracts a zip archive stream into directory.
|
||||
*
|
||||
* If on_extract is not NULL, the callback will be called after
|
||||
* successfully extracted each zip entry.
|
||||
* Returning a negative value from the callback will cause abort and return an
|
||||
* error. The last argument (void *arg) is optional, which you can use to pass
|
||||
* data to the on_extract callback.
|
||||
*
|
||||
* @param stream zip archive stream.
|
||||
* @param size stream size.
|
||||
* @param dir output directory.
|
||||
* @param on_extract on extract callback.
|
||||
* @param arg opaque pointer.
|
||||
*
|
||||
* @return the return code - 0 on success, negative number (< 0) on error.
|
||||
*/
|
||||
extern ZIP_EXPORT int
|
||||
zip_stream_extract(const char *stream, size_t size, const char *dir,
|
||||
int (*on_extract)(const char *filename, void *arg),
|
||||
void *arg);
|
||||
|
||||
/**
|
||||
* Opens zip archive stream into memory.
|
||||
*
|
||||
* @param stream zip archive stream.
|
||||
* @param size stream size.
|
||||
*
|
||||
* @return the zip archive handler or NULL on error
|
||||
*/
|
||||
extern ZIP_EXPORT struct zip_t *zip_stream_open(const char *stream, size_t size,
|
||||
int level, char mode);
|
||||
|
||||
/**
|
||||
* Copy zip archive stream output buffer.
|
||||
*
|
||||
* @param zip zip archive handler.
|
||||
* @param buf output buffer. User should free buf.
|
||||
* @param bufsize output buffer size (in bytes).
|
||||
*
|
||||
* @return copy size
|
||||
*/
|
||||
extern ZIP_EXPORT ssize_t zip_stream_copy(struct zip_t *zip, void **buf,
|
||||
size_t *bufsize);
|
||||
|
||||
/**
|
||||
* Close zip archive releases resources.
|
||||
*
|
||||
* @param zip zip archive handler.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
extern ZIP_EXPORT void zip_stream_close(struct zip_t *zip);
|
||||
|
||||
/**
|
||||
* Creates a new archive and puts files into a single zip archive.
|
||||
*
|
||||
* @param zipname zip archive file.
|
||||
* @param filenames input files.
|
||||
* @param len: number of input files.
|
||||
*
|
||||
* @return the return code - 0 on success, negative number (< 0) on error.
|
||||
*/
|
||||
extern ZIP_EXPORT int zip_create(const char *zipname, const char *filenames[],
|
||||
size_t len);
|
||||
|
||||
/**
|
||||
* Extracts a zip archive file into directory.
|
||||
*
|
||||
* If on_extract_entry is not NULL, the callback will be called after
|
||||
* successfully extracted each zip entry.
|
||||
* Returning a negative value from the callback will cause abort and return an
|
||||
* error. The last argument (void *arg) is optional, which you can use to pass
|
||||
* data to the on_extract_entry callback.
|
||||
*
|
||||
* @param zipname zip archive file.
|
||||
* @param dir output directory.
|
||||
* @param on_extract_entry on extract callback.
|
||||
* @param arg opaque pointer.
|
||||
*
|
||||
* @return the return code - 0 on success, negative number (< 0) on error.
|
||||
*/
|
||||
extern ZIP_EXPORT int zip_extract(const char *zipname, const char *dir,
|
||||
int (*on_extract_entry)(const char *filename,
|
||||
void *arg),
|
||||
void *arg);
|
||||
/** @} */
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
Loading…
Add table
Add a link
Reference in a new issue