694 lines
22 KiB
C++
694 lines
22 KiB
C++
#include "pch.h"
|
|
#include "log.h"
|
|
#include "app.h"
|
|
#include "node_icon.h"
|
|
#include "node_dialog_open.h"
|
|
#include "node_progress_bar.h"
|
|
|
|
#ifdef __APPLE__
|
|
#include <Foundation/Foundation.h>
|
|
#endif
|
|
|
|
#ifdef __ANDROID__
|
|
void android_async_lock(struct engine* engine);
|
|
void android_async_swap(struct engine* engine);
|
|
void android_async_unlock(struct engine* engine);
|
|
#elif _WIN32
|
|
void async_lock();
|
|
void async_swap();
|
|
void async_unlock();
|
|
#endif
|
|
|
|
using namespace ui;
|
|
|
|
App App::I; // singleton
|
|
|
|
void App::create()
|
|
{
|
|
width = 1920/2;
|
|
height = 1080/2;
|
|
}
|
|
|
|
void App::clear()
|
|
{
|
|
glClearColor(.1f, .1f, .1f, 1.f);
|
|
glViewport(0, 0, (GLsizei)width, (GLsizei)height);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
}
|
|
|
|
void App::initAssets()
|
|
{
|
|
LOG("initializing assets");
|
|
FontManager::init();
|
|
LOG("initializing assets loading fonts");
|
|
FontManager::load(kFont::Arial_11, "data/arial.ttf", 15);
|
|
FontManager::load(kFont::Arial_30, "data/arial.ttf", 30);
|
|
|
|
LOG("initializing assets create sampler");
|
|
sampler.create(GL_NEAREST);
|
|
LOG("initializing assets load uvs texture");
|
|
if (!tex.load("data/uvs.jpg"))
|
|
LOG("error loading image");
|
|
LOG("initializing assets completed");
|
|
}
|
|
|
|
void App::initLog()
|
|
{
|
|
#if defined(__IOS__) || defined(__OSX__)
|
|
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
|
|
NSString* docpath = [paths objectAtIndex:0];
|
|
data_path = [docpath cStringUsingEncoding:NSASCIIStringEncoding];
|
|
NSString* recpath = [docpath stringByAppendingString:@"/rec"];
|
|
rec_path = [recpath cStringUsingEncoding:NSASCIIStringEncoding];
|
|
NSError* recerr = nil;
|
|
if (![[NSFileManager defaultManager] createDirectoryAtPath:recpath withIntermediateDirectories:YES attributes:nil error:&recerr])
|
|
{
|
|
LOG("error creating rec path: %s", [[recerr localizedDescription] cStringUsingEncoding:NSASCIIStringEncoding]);
|
|
}
|
|
#elif _WIN32
|
|
//CHAR my_documents[MAX_PATH];
|
|
//HRESULT result = SHGetFolderPathA(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, my_documents);
|
|
|
|
//HMODULE hModule = GetModuleHandle(NULL);
|
|
//CHAR path[MAX_PATH];
|
|
//GetModuleFileNameA(hModule, path, MAX_PATH);
|
|
//CHAR out_drive[MAX_PATH];
|
|
//CHAR out_path[MAX_PATH];
|
|
//_splitpath(path, out_drive, out_path, nullptr, nullptr);
|
|
//sprintf_s(path, "%s%s", out_drive, out_path);
|
|
//data_path = path;
|
|
|
|
CHAR path[MAX_PATH];
|
|
GetCurrentDirectoryA(sizeof(path), path);
|
|
data_path = path;
|
|
rec_path = data_path + "\\frames";
|
|
#endif
|
|
|
|
LogRemote::I.start();
|
|
LogRemote::I.file_init();
|
|
}
|
|
|
|
int progress_callback_download(void *clientp, curl_off_t dltotal,
|
|
curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
|
|
{
|
|
std::function<void(float)> progress = *(std::function<void(float)>*)clientp;
|
|
progress((float)dlnow / (float)dltotal);
|
|
return 0;
|
|
}
|
|
|
|
int progress_callback_upload(void *clientp, curl_off_t dltotal,
|
|
curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
|
|
{
|
|
std::function<void(float)> progress = *(std::function<void(float)>*)clientp;
|
|
progress((float)ulnow / (float)ultotal);
|
|
return 0;
|
|
}
|
|
|
|
void App::download(std::string filename, std::function<void(float)> progress)
|
|
{
|
|
CURL *curl = curl_easy_init();
|
|
if (curl)
|
|
{
|
|
auto dest = data_path + "/" + filename;
|
|
FILE* fp = fopen(dest.c_str(), "wb");
|
|
std::string url = "http://omigamedev.com/panopainter/cloud/cloud-dwl.php?file=" + filename;
|
|
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_data_write);
|
|
if (progress)
|
|
{
|
|
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, progress_callback_download);
|
|
curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &progress);
|
|
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
|
|
}
|
|
auto err = curl_easy_perform(curl);
|
|
curl_easy_cleanup(curl);
|
|
fclose(fp);
|
|
}
|
|
}
|
|
|
|
void App::upload(std::string filename, std::string name, std::function<void(float)> progress)
|
|
{
|
|
CURL *curl;
|
|
|
|
struct curl_httppost *formpost = NULL;
|
|
struct curl_httppost *lastptr = NULL;
|
|
|
|
//curl_global_init(CURL_GLOBAL_ALL);
|
|
|
|
curl_formadd(&formpost,
|
|
&lastptr,
|
|
CURLFORM_COPYNAME, "fileToUpload",
|
|
CURLFORM_FILE, filename.c_str(),
|
|
CURLFORM_END);
|
|
|
|
curl = curl_easy_init();
|
|
std::string res;
|
|
|
|
if (curl)
|
|
{
|
|
std::string url = "http://omigamedev.com/panopainter/cloud/cloud-upl.php?name=" + name;
|
|
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
|
curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &res);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_data_handler);
|
|
if (progress)
|
|
{
|
|
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, progress_callback_upload);
|
|
curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &progress);
|
|
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
|
|
}
|
|
auto err = curl_easy_perform(curl);
|
|
std::cout << "\n\nUPLOAD RESULT\n" << res << "\n\n\n";
|
|
curl_easy_cleanup(curl);
|
|
}
|
|
}
|
|
|
|
void App::init()
|
|
{
|
|
#ifdef _WIN32
|
|
static CONSOLE_SCREEN_BUFFER_INFO info;
|
|
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info);
|
|
// colors: http://stackoverflow.com/questions/4053837/colorizing-text-in-the-console-with-c
|
|
glDebugMessageCallback([](GLenum source, GLenum type, GLuint id,
|
|
GLenum severity, GLsizei length, const GLchar* message, const void* userParam)
|
|
{
|
|
static std::map<GLenum, int> colors = {
|
|
{ GL_DEBUG_SEVERITY_NOTIFICATION, 8 },
|
|
{ GL_DEBUG_SEVERITY_LOW, 8 },
|
|
{ GL_DEBUG_SEVERITY_MEDIUM, FOREGROUND_GREEN | FOREGROUND_INTENSITY },
|
|
{ GL_DEBUG_SEVERITY_HIGH, FOREGROUND_RED | FOREGROUND_INTENSITY },
|
|
};
|
|
if (severity == GL_DEBUG_SEVERITY_HIGH || severity == GL_DEBUG_SEVERITY_MEDIUM || severity == GL_DEBUG_SEVERITY_LOW)
|
|
{
|
|
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), colors[severity]);
|
|
LOG("OPENGL: %.*s", length, message);
|
|
FlushConsoleInputBuffer(GetStdHandle(STD_OUTPUT_HANDLE));
|
|
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), info.wAttributes);
|
|
//__debugbreak();
|
|
}
|
|
}, nullptr);
|
|
glEnable(GL_DEBUG_OUTPUT);
|
|
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
|
|
#endif
|
|
|
|
LOG("GL version: %s", glGetString(GL_VERSION));
|
|
LOG("GL vendor: %s", glGetString(GL_VENDOR));
|
|
LOG("GL renderer: %s", glGetString(GL_RENDERER));
|
|
|
|
// GLint n_exts;
|
|
// glGetIntegerv(GL_NUM_EXTENSIONS, &n_exts);
|
|
// for (int i = 0; i < n_exts; i++)
|
|
// {
|
|
// LOG("%s", glGetStringi(GL_EXTENSIONS, i));
|
|
// }
|
|
|
|
LOG("Screen Resolution: %dx%d", (int)width, (int)height);
|
|
|
|
//zoom = ceilf(width / 2000.f);
|
|
//zoom = 2;
|
|
|
|
glDisable(GL_DEPTH_TEST);
|
|
#if defined(_WIN32) || defined(__OSX__)
|
|
glEnable(GL_PROGRAM_POINT_SIZE);
|
|
#endif
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
glBlendEquation(GL_FUNC_ADD);
|
|
|
|
initShaders();
|
|
initAssets();
|
|
initLayout();
|
|
|
|
|
|
GLfloat width_range[2];
|
|
glGetFloatv(GL_ALIASED_LINE_WIDTH_RANGE, width_range);
|
|
LOG("GL line range: %f - %f", width_range[0], width_range[1]);
|
|
LOG("Screen Size: %f %f", width, height);
|
|
|
|
GLint fb0;
|
|
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &fb0);
|
|
LOG("Default Framebuffer %d", fb0);
|
|
}
|
|
|
|
void App::async_start()
|
|
{
|
|
#if __OSX__
|
|
[osx_view async_lock];
|
|
#elif __IOS__
|
|
[ios_view async_lock];
|
|
#elif __ANDROID__
|
|
android_async_lock(and_engine);
|
|
#elif _WIN32
|
|
async_lock();
|
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
#endif
|
|
}
|
|
|
|
void App::async_update()
|
|
{
|
|
#if __IOS__
|
|
[ios_view->glview bindDrawable];
|
|
#elif _WIN32
|
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
#endif
|
|
redraw = true;
|
|
clear();
|
|
update(0);
|
|
#if __OSX__
|
|
[osx_view async_swap];
|
|
#elif __IOS__
|
|
[ios_view->glview bindDrawable];
|
|
[ios_view async_swap];
|
|
#elif __ANDROID__
|
|
android_async_swap(and_engine);
|
|
#elif _WIN32
|
|
async_swap();
|
|
#endif
|
|
}
|
|
|
|
void App::async_redraw()
|
|
{
|
|
redraw = true;
|
|
}
|
|
|
|
void App::async_end()
|
|
{
|
|
#if __OSX__
|
|
[osx_view async_unlock];
|
|
#elif __IOS__
|
|
[ios_view async_unlock];
|
|
#elif __ANDROID__
|
|
android_async_unlock(and_engine);
|
|
#elif _WIN32
|
|
async_unlock();
|
|
#endif
|
|
}
|
|
|
|
void App::update(float dt)
|
|
{
|
|
static float rec_timer = 0.f;
|
|
|
|
static std::mutex m;
|
|
std::lock_guard<std::mutex> _lock(m);
|
|
|
|
// update offscreen stuff
|
|
if (canvas && canvas->m_canvas)
|
|
canvas->m_canvas->stroke_draw();
|
|
|
|
if (!(redraw || animate))
|
|
return;
|
|
|
|
//glClearColor(.1f, .1f, .1f, 1.f);
|
|
//glViewport(0, 0, (GLsizei)width, (GLsizei)height);
|
|
//glClear(GL_COLOR_BUFFER_BIT);
|
|
|
|
//if (!canvas->m_mouse_captured)
|
|
{
|
|
#if _WIN32 || __OSX__
|
|
layout.reload();
|
|
#endif
|
|
if (auto* main = layout[main_id])
|
|
{
|
|
main->update(width, height, zoom);
|
|
stroke->update_controls();
|
|
}
|
|
}
|
|
|
|
static glm::vec4 color_button_normal{.1, .1, .1, 1};
|
|
static glm::vec4 color_button_hlight{ 1, .0, .0, 1};
|
|
CanvasModePen* mode = (CanvasModePen*)canvas->m_canvas->modes[(int)Canvas::kCanvasMode::Draw][0];
|
|
layout[main_id]->find<NodeButton>("btn-pick")->set_color(
|
|
mode->m_picking ? color_button_hlight : color_button_normal);
|
|
layout[main_id]->find<NodeButton>("btn-touchlock")->set_color(
|
|
canvas->m_canvas->m_touch_lock ? color_button_hlight : color_button_normal);
|
|
|
|
|
|
auto observer = [this](Node* n)
|
|
{
|
|
if (n && n->m_display)
|
|
{
|
|
auto box = n->m_clip;
|
|
//glm::ivec4 c = glm::vec4((int)box.x - 1, (int)(height / zoom - box.y - box.w) - 1, (int)box.z + 2, (int)box.w + 2) * zoom;
|
|
glm::ivec4 c = glm::vec4((int)box.x, (int)(height / zoom - box.y - box.w), (int)box.z, (int)box.w) * zoom;
|
|
glScissor(c.x, c.y, c.z, c.w);
|
|
n->draw();
|
|
}
|
|
};
|
|
//glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
|
#if __IOS__
|
|
[ios_view->glview bindDrawable];
|
|
#else
|
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
#endif
|
|
glEnable(GL_SCISSOR_TEST);
|
|
if (auto* main = layout[main_id])
|
|
main->watch(observer);
|
|
//msgbox->watch(observer);
|
|
glDisable(GL_SCISSOR_TEST);
|
|
|
|
if (rec_running)
|
|
{
|
|
rec_timer += dt;
|
|
if (rec_timer > 1.f && canvas->m_canvas->m_dirty_stroke)
|
|
{
|
|
canvas->m_canvas->m_dirty_stroke = false;
|
|
|
|
LOG("rec tick");
|
|
rec_timer = 0.f;
|
|
|
|
auto data = new uint8_t[width * height * 4];
|
|
#if __IOS__
|
|
[ios_view->glview bindDrawable];
|
|
#else
|
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
#endif
|
|
|
|
GLint dfbo, rfbo;
|
|
glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &rfbo);
|
|
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &dfbo);
|
|
if (dfbo != rfbo)
|
|
LOG("DIFFERENT FB");
|
|
|
|
glReadBuffer(GL_FRONT);
|
|
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data);
|
|
{
|
|
std::lock_guard<std::mutex> lock(rec_mutex);
|
|
rec_frames.emplace_back(data);
|
|
rec_cv.notify_all();
|
|
}
|
|
update_rec_frames();
|
|
}
|
|
}
|
|
|
|
redraw = false;
|
|
}
|
|
|
|
|
|
void App::terminate()
|
|
{
|
|
LOG("App::terminate");
|
|
TextureManager::invalidate();
|
|
ShaderManager::invalidate();
|
|
layout.clear_context();
|
|
brushes->clear_context();
|
|
layers->clear_context();
|
|
color->clear_context();
|
|
stroke->clear_context();
|
|
rec_stop();
|
|
}
|
|
|
|
void App::update_memory_usage(size_t bytes)
|
|
{
|
|
if (auto txt = layout[main_id]->find<NodeText>("txt-memory"))
|
|
{
|
|
static char buffer[128];
|
|
sprintf(buffer, "History memory: %.2f Mb", bytes / 1024.f / 1024.f);
|
|
txt->set_text(buffer);
|
|
layout[main_id]->update();
|
|
}
|
|
}
|
|
|
|
void App::update_rec_frames()
|
|
{
|
|
if (auto txt = layout[main_id]->find<NodeText>("txt-rec"))
|
|
{
|
|
if (rec_running)
|
|
{
|
|
static char buffer[128];
|
|
sprintf(buffer, "Recorder %d frames", rec_count);
|
|
txt->set_text(buffer);
|
|
}
|
|
else
|
|
{
|
|
txt->set_text("");
|
|
}
|
|
layout[main_id]->update();
|
|
}
|
|
}
|
|
|
|
void App::rec_clear()
|
|
{
|
|
rec_stop();
|
|
#if defined(__IOS__) || defined(__OSX__)
|
|
NSString *path = [NSString stringWithUTF8String:rec_path.c_str()];
|
|
NSDirectoryEnumerator* en = [[NSFileManager defaultManager] enumeratorAtPath:path];
|
|
NSError* err = nil;
|
|
BOOL res;
|
|
NSString* file;
|
|
while (file = [en nextObject])
|
|
{
|
|
NSString* file_path = [path stringByAppendingPathComponent:file];
|
|
[[NSFileManager defaultManager] removeItemAtPath:file_path error:nil];
|
|
NSLog(@"delete: %@", file_path);
|
|
}
|
|
#endif
|
|
rec_count = 0;
|
|
update_rec_frames();
|
|
}
|
|
|
|
void App::rec_start()
|
|
{
|
|
if (!rec_running)
|
|
{
|
|
#if defined(__IOS__) || defined(__OSX__)
|
|
NSString* path = [NSString stringWithUTF8String:rec_path.c_str()];
|
|
NSArray *dirFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil];
|
|
NSArray *jpgFiles = [dirFiles filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"self ENDSWITH '.jpg'"]];
|
|
rec_count = (int)[jpgFiles count];
|
|
update_rec_frames();
|
|
rec_thread = std::thread(&App::rec_loop, this);
|
|
#else
|
|
rec_count = Asset::list_files(rec_path, false, ".*\\.jpg").size();
|
|
update_rec_frames();
|
|
rec_thread = std::thread(&App::rec_loop, this);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void App::rec_stop()
|
|
{
|
|
if (rec_running)
|
|
{
|
|
rec_running = false;
|
|
rec_cv.notify_all();
|
|
if (rec_thread.joinable())
|
|
rec_thread.join();
|
|
update_rec_frames();
|
|
}
|
|
}
|
|
|
|
void stillImageDataReleaseCallback(void *releaseRefCon, const void *baseAddress)
|
|
{
|
|
free((void *)baseAddress);
|
|
}
|
|
|
|
void App::rec_export(std::string path)
|
|
{
|
|
int progress = 0;
|
|
int tot = rec_count;
|
|
auto pb = layout[main_id]->add_child<NodeProgressBar>();
|
|
pb->m_progress->SetWidthP(0);
|
|
pb->m_title->set_text("Exporting MP4 movie");
|
|
async_update();
|
|
|
|
#if defined(__IOS__) || defined(__OSX__)
|
|
NSString* mov_path = [NSString stringWithFormat:@"%s/out.mp4", rec_path.c_str()];
|
|
if ([[NSFileManager defaultManager] fileExistsAtPath:mov_path])
|
|
{
|
|
NSLog(@"remove existing mp4");
|
|
[[NSFileManager defaultManager] removeItemAtPath:mov_path error:nil];
|
|
}
|
|
NSURL* url = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%s/out.mp4", rec_path.c_str()]];
|
|
AVAssetWriter* writer = [AVAssetWriter assetWriterWithURL:url fileType:AVFileTypeMPEG4 error:nil];
|
|
writer.shouldOptimizeForNetworkUse = NO;
|
|
NSDictionary *videoCompressionSettings = @{
|
|
AVVideoCodecKey : AVVideoCodecH264,
|
|
AVVideoWidthKey : @(width),
|
|
AVVideoHeightKey : @(height),
|
|
AVVideoCompressionPropertiesKey : @{ AVVideoAverageBitRateKey : @(8<<20) }
|
|
};
|
|
if (![writer canApplyOutputSettings:videoCompressionSettings forMediaType:AVMediaTypeVideo])
|
|
{
|
|
NSLog(@"Couldn't add asset writer video input.");
|
|
return;
|
|
}
|
|
AVAssetWriterInput* input = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo
|
|
outputSettings:videoCompressionSettings
|
|
sourceFormatHint:nil];
|
|
input.expectsMediaDataInRealTime = YES;
|
|
|
|
NSDictionary *adaptorDict = @{
|
|
(id)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA),
|
|
(id)kCVPixelBufferWidthKey : @(width),
|
|
(id)kCVPixelBufferHeightKey : @(height)
|
|
};
|
|
|
|
AVAssetWriterInputPixelBufferAdaptor* _pixelBufferAdaptor = [[AVAssetWriterInputPixelBufferAdaptor alloc]
|
|
initWithAssetWriterInput:input
|
|
sourcePixelBufferAttributes:adaptorDict];
|
|
|
|
|
|
// Add asset writer input to asset writer
|
|
if (![writer canAddInput:input]) {
|
|
NSLog(@"Couldn't add input to writer.");
|
|
return;
|
|
}
|
|
|
|
[writer addInput:input];
|
|
|
|
CMTime t;
|
|
t.timescale = 30;
|
|
t.flags = kCMTimeFlags_Valid;
|
|
t.epoch = 0;
|
|
t.value = 0;
|
|
//[writer startSessionAtSourceTime:t];
|
|
|
|
CVPixelBufferRef buff = NULL;
|
|
uint8_t* data = (uint8_t*)calloc(1, width * height * 4);
|
|
CVPixelBufferCreateWithBytes(kCFAllocatorDefault, width, height, kCVPixelFormatType_32RGBA, data,
|
|
width * 4, stillImageDataReleaseCallback, nil, nil, &buff);
|
|
OSStatus err = CVPixelBufferPoolCreatePixelBuffer(nil, _pixelBufferAdaptor.pixelBufferPool, &buff);
|
|
|
|
|
|
if (writer.status == AVAssetWriterStatusUnknown)
|
|
{
|
|
// If the asset writer status is unknown, implies writing hasn't started yet, hence start writing with start time as the buffer's presentation timestamp
|
|
if ([writer startWriting])
|
|
{
|
|
[writer startSessionAtSourceTime:t];
|
|
}
|
|
}
|
|
|
|
if (writer.status == AVAssetWriterStatusWriting)
|
|
{
|
|
for (int i = 0; i < tot; i++)
|
|
{
|
|
// If the asset writer status is writing, append sample buffer to its corresponding asset writer input
|
|
if (input.readyForMoreMediaData)
|
|
{
|
|
char path[256];
|
|
snprintf(path, sizeof(path), "%s/%04d.jpg", rec_path.c_str(), i);
|
|
NSString* img_path = [NSString stringWithUTF8String:path];
|
|
NSLog(@"frame: %@", img_path);
|
|
#if __OSX__
|
|
NSImage *image = [[NSImage alloc] initWithContentsOfFile:img_path];
|
|
if (!image)
|
|
break;
|
|
NSRect imageRect = NSMakeRect(0, 0, image.size.width, image.size.height);
|
|
CGImageRef cgImage = [image CGImageForProposedRect:&imageRect context:NULL hints:nil];
|
|
#elif __IOS__
|
|
UIImage* image = [UIImage imageNamed:img_path];
|
|
if (!image)
|
|
break;
|
|
CGImageRef cgImage = image.CGImage;
|
|
#endif
|
|
|
|
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
|
|
[NSNumber numberWithBool:YES], kCVPixelBufferCGImageCompatibilityKey,
|
|
[NSNumber numberWithBool:YES], kCVPixelBufferCGBitmapContextCompatibilityKey, nil];
|
|
CGSize sz = image.size;
|
|
CVPixelBufferRef pxbuffer = NULL;
|
|
{
|
|
CGImageRef image = cgImage;
|
|
CGSize size = sz;
|
|
CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault, size.width, size.height, kCVPixelFormatType_32ARGB, (__bridge CFDictionaryRef) options, &pxbuffer);
|
|
// CVReturn status = CVPixelBufferPoolCreatePixelBuffer(NULL, adaptor.pixelBufferPool, &pxbuffer);
|
|
|
|
//NSParameterAssert(status == kCVReturnSuccess && pxbuffer != NULL);
|
|
|
|
CVPixelBufferLockBaseAddress(pxbuffer, 0);
|
|
void *pxdata = CVPixelBufferGetBaseAddress(pxbuffer);
|
|
//NSParameterAssert(pxdata != NULL);
|
|
|
|
CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
|
|
CGContextRef context = CGBitmapContextCreate(pxdata, size.width, size.height, 8, 4*size.width, rgbColorSpace, kCGImageAlphaPremultipliedFirst);
|
|
//NSParameterAssert(context);
|
|
|
|
CGContextDrawImage(context, CGRectMake(0, 0, CGImageGetWidth(image), CGImageGetHeight(image)), image);
|
|
|
|
CGColorSpaceRelease(rgbColorSpace);
|
|
CGContextRelease(context);
|
|
|
|
CVPixelBufferUnlockBaseAddress(pxbuffer, 0);
|
|
}
|
|
|
|
t.value = i;
|
|
if (![_pixelBufferAdaptor appendPixelBuffer:pxbuffer withPresentationTime:t])
|
|
{
|
|
NSLog(@"error %@", [writer.error localizedFailureReason]);
|
|
}
|
|
CFRelease(pxbuffer);
|
|
|
|
progress++;
|
|
pb->m_progress->SetWidthP((float)progress / tot * 100.f);
|
|
async_update();
|
|
}
|
|
}
|
|
[input markAsFinished];
|
|
[writer finishWritingWithCompletionHandler:^{
|
|
NSString* path = [NSString stringWithFormat:@"%s/out.mp4", rec_path.c_str()];
|
|
NSLog(@"saved video %@", path);
|
|
#if __IOS__
|
|
if (UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(path))
|
|
{
|
|
NSLog(@"saving to camera roll");
|
|
UISaveVideoAtPathToSavedPhotosAlbum(path, nil, nil, nil);
|
|
}
|
|
#endif
|
|
}];
|
|
}
|
|
|
|
if (writer.status == AVAssetWriterStatusFailed)
|
|
{
|
|
NSLog(@"failed");
|
|
}
|
|
|
|
#endif
|
|
|
|
pb->destroy();
|
|
async_update();
|
|
|
|
}
|
|
|
|
void App::rec_loop()
|
|
{
|
|
rec_running = true;
|
|
while(rec_running)
|
|
{
|
|
std::unique_lock<std::mutex> lock(rec_mutex);
|
|
rec_cv.wait(lock);
|
|
if (!rec_running)
|
|
break;
|
|
if (!rec_frames.empty())
|
|
{
|
|
if (rec_frames.front())
|
|
{
|
|
auto inverted = std::make_unique<uint8_t[]>(width*height*4);
|
|
for (int y = height - 1, y1 = 0; y >= 0; y--, y1++)
|
|
{
|
|
uint8_t* dst = &inverted[y * width * 4];
|
|
uint8_t* src = &rec_frames.front()[y1 * width * 4];
|
|
std::copy_n(src, (int)width * 4, dst);
|
|
}
|
|
char path[256];
|
|
snprintf(path, sizeof(path), "%s/%04d.jpg", rec_path.c_str(), rec_count);
|
|
LOG("writing %s", path);
|
|
jpge::params params;
|
|
params.m_quality = 75;
|
|
bool saved = jpge::compress_image_to_jpeg_file(path, width, height, 4, inverted.get(), params);
|
|
if (!saved)
|
|
{
|
|
LOG("error writing the frame");
|
|
rec_running = false;
|
|
}
|
|
else
|
|
{
|
|
rec_count++;
|
|
redraw = true;
|
|
}
|
|
}
|
|
rec_frames.pop_front();
|
|
}
|
|
}
|
|
}
|