#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 #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 bool async_lock_try(); void async_lock(); void async_swap(); void async_unlock(); void destroy_window(); #endif App App::I; // singleton void App::create() { width = 1920/2; height = 1080/2; } void App::open_document(std::string path) { auto start = path.rfind('/') + 1; doc_name = path.substr(start, path.length() - start - strlen(".ppi")); doc_dir = path.substr(0, start - 1); doc_path = path; canvas->reset_camera(); layers->clear(); canvas->m_canvas->project_open(path, [this](bool success){ // on complete if (success) { async_start(); title_update(); for (auto& i : canvas->m_canvas->m_order) { auto l = layers->add_layer(canvas->m_canvas->m_layers[i].m_name.c_str()); l->m_visibility->set_value(canvas->m_canvas->m_layers[i].m_visible); } async_end(); } else { message_box("Open Document Error", "There was an error opening the document.\n" "It may be inaccessible or corrupted."); } }); ActionManager::clear(); } bool App::request_close() { static bool dialog_already_opened = false; if (!Canvas::I->m_unsaved) return true; if (!dialog_already_opened) { async_start(); auto* m = layout[main_id]->add_child(); m->m_title->set_text("Unsaved document"); m->m_message->set_text("Do you want to close without saving?"); m->btn_ok->m_text->set_text("Yes"); m->btn_ok->on_click = [this](Node*) { #ifdef _WIN32 destroy_window(); PostQuitMessage(0); #endif #ifdef __OSX__ [osx_view close]; #endif Canvas::I->m_unsaved = false; }; m->btn_cancel->m_text->set_text("No"); m->btn_cancel->on_click = [this,m](Node*) { m->destroy(); dialog_already_opened = false; }; async_redraw(); async_end(); dialog_already_opened = true; } return false; } void App::clear() { glClearColor(.1f, .1f, .1f, 1.f); glViewport(off_x, off_y, (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/fonts/Roboto-Regular.ttf", 17); FontManager::load(kFont::Arial_30, "data/fonts/Roboto-Regular.ttf", 30); LOG("initializing assets create sampler"); sampler.create(GL_NEAREST); sampler_stencil.create(GL_LINEAR, GL_REPEAT); sampler_linear.create(GL_LINEAR); m_face_plane.create<1>(2, 2); //sphere.create<8, 8>(1); LOG("initializing assets load uvs texture"); LOG("initializing assets completed"); } void App::initLog() { #if defined(__IOS__) NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString* docpath = (NSString*)[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 defined(__OSX__) NSArray* paths = NSSearchPathForDirectoriesInDomains(NSPicturesDirectory, NSUserDomainMask, YES); NSString* docpath = [(NSString*)[paths objectAtIndex:0] stringByAppendingString:@"/PanoPainter"]; NSError* docerr = nil; if (![[NSFileManager defaultManager] createDirectoryAtPath:docpath withIntermediateDirectories:YES attributes:nil error:&docerr]) { LOG("error creating rec path: %s", [[docerr localizedDescription] cStringUsingEncoding:NSASCIIStringEncoding]); } 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 defined(_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 my_documents[MAX_PATH]; HRESULT result = SHGetFolderPathA(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, my_documents); if (SUCCEEDED(result)) { std::string path = std::string(my_documents) + "\\PanoPainter"; if (!PathFileExistsA(path.c_str())) CreateDirectoryA(path.c_str(), NULL); data_path = path; } else { CHAR path[MAX_PATH]; GetCurrentDirectoryA(sizeof(path), path); data_path = path; } rec_path = data_path + "\\frames"; if (!PathFileExistsA(rec_path.c_str())) CreateDirectoryA(rec_path.c_str(), NULL); #endif // TODO: save this path somewhere in the settings, don't overwrite every start work_path = data_path; //LogRemote::I.start(); LogRemote::I.file_init(); LOG("%s", g_version); } int progress_callback_download(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { std::function progress = *(std::function*)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 progress = *(std::function*)clientp; progress((float)ulnow / (float)ultotal); return 0; } void App::download(std::string url, std::string dest_filepath, std::function progress) { CURL *curl = curl_easy_init(); if (curl) { FILE* fp = fopen(dest_filepath.c_str(), "wb"); LOG("download %s to %s", url.c_str(), dest_filepath.c_str()); 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); #ifdef __ANDROID__ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); #endif 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); } } bool App::check_license() { return true; // TODO: for distribuiton only CURL *curl = curl_easy_init(); if (curl) { std::string url = "https://panopainter.com/license/7565D057-ACBE-4721-9A4E-F342D3DDB7D8.php"; //std::string url = "https://panopainter.com/license/E8EDC2FE-E1BD-4AB1-91BD-FCCD926739BD.php"; // wacom //std::string url = "https://panopainter.com/license/A744FBA9-BC6C-43C8-BD24-0CCE24B3D985.php"; // others std::string ret; curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ret); //curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_data_handler); curl_easy_setopt(curl, CURLOPT_TIMEOUT, 2L); #ifdef __ANDROID__ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); #endif auto err = curl_easy_perform(curl); curl_easy_cleanup(curl); LOG("License check: %s", ret.c_str()); if (err == CURLcode::CURLE_OK && ret == "success") return true; } return false; } void App::upload(std::string filename, std::string name, std::function 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 = "https://panopainter.com/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); #ifdef __ANDROID__ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); #endif 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 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); #ifdef _WIN32 if (severity == GL_DEBUG_SEVERITY_HIGH) __debugbreak(); #endif } }, 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++) { std::string ext = (const char*)glGetStringi(GL_EXTENSIONS, i); //if (ext.find("debug") != std::string::npos) { 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); glEnable(GL_LINE_SMOOTH); #endif glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendEquationSeparate(GL_FUNC_ADD, GL_MAX); initShaders(); initAssets(); initLayout(); title_update(); if (!check_license()) { message_box("License", "Could not validate this license, running in demo mode."); } //GLfloat width_range[2]; //glGetFloatv(GL_SMOOTH_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 _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("btn-pick")->set_color( mode->m_picking ? color_button_hlight : color_button_normal); layout[main_id]->find("btn-pen")->set_color( canvas->m_canvas->m_current_mode == Canvas::kCanvasMode::Draw ? color_button_hlight : color_button_normal); layout[main_id]->find("btn-erase")->set_color( canvas->m_canvas->m_current_mode == Canvas::kCanvasMode::Erase ? color_button_hlight : color_button_normal); layout[main_id]->find("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; Node* p = n->parent; while (p) { float pt = YGNodeLayoutGetPadding(p->y_node, YGEdgeTop); float pr = YGNodeLayoutGetPadding(p->y_node, YGEdgeRight); float pb = YGNodeLayoutGetPadding(p->y_node, YGEdgeBottom); float pl = YGNodeLayoutGetPadding(p->y_node, YGEdgeLeft); glm::vec2 off_p(pl, pt); glm::vec2 off_s(pr, pb); glm::vec4 pclip = { xy(p->m_clip) + off_p, zw(p->m_clip) - off_s - off_p }; box = rect_intersection(box, pclip); p = p->parent; } //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 + off_x, c.y + off_y, c.z, c.w); n->draw(); return true; } return false; }; /* uirtt.bindFramebuffer(); uirtt.clear(); glEnable(GL_SCISSOR_TEST); for (int i = 1; i < layout[main_id]->m_children.size(); i++) layout[main_id]->m_children[i]->watch(observer); //msgbox->watch(observer); glDisable(GL_SCISSOR_TEST); uirtt.unbindFramebuffer(); */ #if __IOS__ [ios_view->glview bindDrawable]; #else glBindFramebuffer(GL_FRAMEBUFFER, 0); #endif //canvas->watch(observer); glEnable(GL_SCISSOR_TEST); for (int i = 0; i < layout[main_id]->m_children.size(); i++) layout[main_id]->m_children[i]->watch(observer); //msgbox->watch(observer); glDisable(GL_SCISSOR_TEST); // glEnable(GL_BLEND); // sampler.bind(0); // ShaderManager::use(kShader::Texture); // ShaderManager::u_int(kShaderUniform::Tex, 0); // ShaderManager::u_mat4(kShaderUniform::MVP, glm::ortho(-1.f, 1.f, -1.f, 1.f)); // glActiveTexture(GL_TEXTURE0); // uirtt.bindTexture(); // m_face_plane.draw_fill(); // uirtt.unbindTexture(); // glDisable(GL_BLEND); 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 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("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("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(); } } int App::res_from_index(int i) { return res_map[i]; } int App::res_to_index(int res) { return std::distance(res_map.begin(), std::find(res_map.begin(), res_map.end(), res)); } std::string App::res_to_string(int res) { return res_map_str[res_to_index(res)]; } 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(); 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 lock(rec_mutex); rec_cv.wait(lock); if (!rec_running) break; if (!rec_frames.empty()) { if (rec_frames.front()) { auto inverted = std::make_unique(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(); } } }