/// <summary> /// Save the current render results to a file. /// This could benefit from QImageWriter, however its compression capabilities are /// severely lacking. A Png file comes out larger than a bitmap, so instead use the /// Png and Jpg wrapper functions from the command line programs. /// This will embed the id, url and nick fields from the options in the image comments. /// </summary> /// <param name="filename">The full path and filename</param> /// <param name="comments">The comments to save in the png or jpg</param> /// <param name="pixels">The buffer containing the pixels</param> /// <param name="width">The width in pixels of the image</param> /// <param name="height">The height in pixels of the image</param> /// <param name="channels">The number of channels, 3 or 4.</param> /// <param name="bpc">The bytes per channel, almost always 1.</param> void FractoriumEmberControllerBase::SaveCurrentRender(const QString& filename, const EmberImageComments& comments, vector<byte>& pixels, size_t width, size_t height, size_t channels, size_t bpc) { if (filename != "") { bool b = false; uint i, j; byte* data = nullptr; vector<byte> vecRgb; QFileInfo fileInfo(filename); QString suffix = fileInfo.suffix(); FractoriumSettings* settings = m_Fractorium->m_Settings; //Ensure dimensions are valid. if (pixels.size() < (width * height * channels * bpc)) { m_Fractorium->ShowCritical("Save Failed", "Dimensions didn't match, not saving.", true); return; } data = pixels.data();//Png and channels == 4. if ((suffix == "jpg" || suffix == "bmp") && channels) { RgbaToRgb(pixels, vecRgb, width, height); data = vecRgb.data(); } string s = filename.toStdString(); string id = settings->Id().toStdString(); string url = settings->Url().toStdString(); string nick = settings->Nick().toStdString(); if (suffix == "png") b = WritePng(s.c_str(), data, width, height, 1, true, comments, id, url, nick); else if (suffix == "jpg") b = WriteJpeg(s.c_str(), data, width, height, 100, true, comments, id, url, nick); else if (suffix == "bmp") b = WriteBmp(s.c_str(), data, width, height); else { m_Fractorium->ShowCritical("Save Failed", "Unrecognized format " + suffix + ", not saving.", true); return; } if (b) settings->SaveFolder(fileInfo.canonicalPath()); else m_Fractorium->ShowCritical("Save Failed", "Could not save file, try saving to a different folder.", true); } }
bool EmberRender(EmberOptions& opt) { #ifdef USECL EmberCLns::OpenCLInfo& info(EmberCLns::OpenCLInfo::Instance());#endif std::cout.imbue(std::locale("")); if (opt.DumpArgs()) cout << opt.GetValues(OPT_USE_RENDER) << endl; if (opt.OpenCLInfo()) { cout << "\nOpenCL Info: " << endl; cout << info.DumpInfo(); return true; } Timing t; bool writeSuccess = false; byte* finalImagep; uint padding; size_t i, channels; size_t strips; size_t iterCount; string filename; string inputPath = GetPath(opt.Input()); ostringstream os; pair<size_t, size_t> p; vector<Ember<T>> embers; vector<byte> finalImage; EmberStats stats; EmberReport emberReport; EmberImageComments comments; XmlToEmber<T> parser; EmberToXml<T> emberToXml; vector<QTIsaac<ISAAC_SIZE, ISAAC_INT>> randVec; const vector<pair<size_t, size_t>> devices = Devices(opt.Devices()); unique_ptr<RenderProgress<T>> progress(new RenderProgress<T>()); unique_ptr<Renderer<T, float>> renderer(CreateRenderer<T>(opt.EmberCL() ? OPENCL_RENDERER : CPU_RENDERER, devices, false, 0, emberReport)); vector<string> errorReport = emberReport.ErrorReport(); if (!errorReport.empty()) emberReport.DumpErrorReport(); if (!renderer.get()) { cout << "Renderer creation failed, exiting." << endl; return false; } if (opt.EmberCL() && renderer->RendererType() != OPENCL_RENDERER)//OpenCL init failed, so fall back to CPU. opt.EmberCL(false); if (!InitPaletteList<T>(opt.PalettePath())) return false; if (!ParseEmberFile(parser, opt.Input(), embers)) return false; if (!opt.EmberCL()) { if (opt.ThreadCount() == 0) { cout << "Using " << Timing::ProcessorCount() << " automatically detected threads." << endl; opt.ThreadCount(Timing::ProcessorCount()); } else { cout << "Using " << opt.ThreadCount() << " manually specified threads." << endl; } renderer->ThreadCount(opt.ThreadCount(), opt.IsaacSeed() != "" ? opt.IsaacSeed().c_str() : nullptr); } else { cout << "Using OpenCL to render." << endl; if (opt.Verbose()) { for (auto& device : devices) { cout << "Platform: " << info.PlatformName(device.first) << endl; cout << "Device: " << info.DeviceName(device.first, device.second) << endl; } } if (opt.ThreadCount() > 1) cout << "Cannot specify threads with OpenCL, using 1 thread." << endl; opt.ThreadCount(1); renderer->ThreadCount(opt.ThreadCount(), opt.IsaacSeed() != "" ? opt.IsaacSeed().c_str() : nullptr); if (opt.BitsPerChannel() != 8) { cout << "Bits per channel cannot be anything other than 8 with OpenCL, setting to 8." << endl; opt.BitsPerChannel(8); } } if (opt.Format() != "jpg" && opt.Format() != "png" && opt.Format() != "ppm" && opt.Format() != "bmp") { cout << "Format must be jpg, png, ppm, or bmp not " << opt.Format() << ". Setting to jpg." << endl; } channels = opt.Format() == "png" ? 4 : 3; if (opt.BitsPerChannel() == 16 && opt.Format() != "png") { cout << "Support for 16 bits per channel images is only present for the png format. Setting to 8." << endl; opt.BitsPerChannel(8); } else if (opt.BitsPerChannel() != 8 && opt.BitsPerChannel() != 16) { cout << "Unexpected bits per channel specified " << opt.BitsPerChannel() << ". Setting to 8." << endl; opt.BitsPerChannel(8); } if (opt.InsertPalette() && opt.BitsPerChannel() != 8) { cout << "Inserting palette only supported with 8 bits per channel, insertion will not take place." << endl; opt.InsertPalette(false); } if (opt.AspectRatio() < 0) { cout << "Invalid pixel aspect ratio " << opt.AspectRatio() << endl << ". Must be positive, setting to 1." << endl; opt.AspectRatio(1); } if (!opt.Out().empty() && (embers.size() > 1)) { cout << "Single output file " << opt.Out() << " specified for multiple images. Changing to use prefix of badname-changethis instead. Always specify prefixes when reading a file with multiple embers." << endl; opt.Out(""); opt.Prefix("badname-changethis"); } //Final setup steps before running. os.imbue(std::locale("")); padding = uint(std::log10(double(embers.size()))) + 1; renderer->EarlyClip(opt.EarlyClip()); renderer->YAxisUp(opt.YAxisUp()); renderer->LockAccum(opt.LockAccum()); renderer->InsertPalette(opt.InsertPalette()); renderer->PixelAspectRatio(T(opt.AspectRatio())); renderer->Transparency(opt.Transparency()); renderer->NumChannels(channels); renderer->BytesPerChannel(opt.BitsPerChannel() / 8); renderer->Priority(eThreadPriority(Clamp<intmax_t>(intmax_t(opt.Priority()), intmax_t(eThreadPriority::LOWEST), intmax_t(eThreadPriority::HIGHEST)))); renderer->Callback(opt.DoProgress() ? progress.get() : nullptr); for (i = 0; i < embers.size(); i++) { if (opt.Verbose() && embers.size() > 1) cout << "\nFlame = " << i + 1 << "/" << embers.size() << endl; else if (embers.size() > 1) VerbosePrint(endl); if (opt.Supersample() > 0) embers[i].m_Supersample = opt.Supersample(); if (opt.SubBatchSize() != DEFAULT_SBS) embers[i].m_SubBatchSize = opt.SubBatchSize(); embers[i].m_TemporalSamples = 1;//Force temporal samples to 1 for render. embers[i].m_Quality *= T(opt.QualityScale()); embers[i].m_FinalRasW = size_t(T(embers[i].m_FinalRasW) * opt.SizeScale()); embers[i].m_FinalRasH = size_t(T(embers[i].m_FinalRasH) * opt.SizeScale()); embers[i].m_PixelsPerUnit *= T(opt.SizeScale()); if (embers[i].m_FinalRasW == 0 || embers[i].m_FinalRasH == 0) { cout << "Output image " << i << " has dimension 0: " << embers[i].m_FinalRasW << ", " << embers[i].m_FinalRasH << ". Setting to 1920 x 1080." << endl; embers[i].m_FinalRasW = 1920; embers[i].m_FinalRasH = 1080; } //Cast to double in case the value exceeds 2^32. double imageMem = double(renderer->NumChannels()) * double(embers[i].m_FinalRasW) * double(embers[i].m_FinalRasH) * double(renderer->BytesPerChannel()); double maxMem = pow(2.0, double((sizeof(void*) * 8) - 1)); if (imageMem > maxMem)//Ensure the max amount of memory for a process is not exceeded. { cout << "Image " << i << " size > " << maxMem << ". Setting to 1920 x 1080." << endl; embers[i].m_FinalRasW = 1920; embers[i].m_FinalRasH = 1080; } stats.Clear(); renderer->SetEmber(embers[i]); renderer->PrepFinalAccumVector(finalImage);//Must manually call this first because it could be erroneously made smaller due to strips if called inside Renderer::Run(). if (opt.Strips() > 1) { strips = opt.Strips(); } else { p = renderer->MemoryRequired(1, true, false);//No threaded write for render, only for animate. strips = CalcStrips(double(p.second), double(renderer->MemoryAvailable()), opt.UseMem()); if (strips > 1) VerbosePrint("Setting strips to " << strips << " with specified memory usage of " << opt.UseMem()); } strips = VerifyStrips(embers[i].m_FinalRasH, strips, [&](const string& s) { cout << s << endl; },//Greater than height. [&](const string& s) { cout << s << endl; },//Mod height != 0. [&](const string& s) { cout << s << endl; });//Final strips value to be set. //For testing incremental renderer. //int sb = 1; //bool resume = false, success = false; //do //{ // success = renderer->Run(finalImage, 0, sb, false/*resume == false*/) == RENDER_OK; // sb++; // resume = true; //} //while (success && renderer->ProcessState() != ACCUM_DONE); StripsRender<T>(renderer.get(), embers[i], finalImage, 0, strips, opt.YAxisUp(), [&](size_t strip)//Pre strip. { if (opt.Verbose() && (strips > 1) && strip > 0) cout << endl; if (strips > 1) VerbosePrint("Strip = " << (strip + 1) << "/" << strips); }, [&](size_t strip)//Post strip. { progress->Clear(); stats += renderer->Stats(); }, [&](size_t strip)//Error. { cout << "Error: image rendering failed, skipping to next image." << endl; renderer->DumpErrorReport();//Something went wrong, print errors. }, //Final strip. //Original wrote every strip as a full image which could be very slow with many large images. //Only write once all strips for this image are finished. [&](Ember<T>& finalEmber) { if (!opt.Out().empty()) { filename = opt.Out(); } else if (opt.NameEnable() && !finalEmber.m_Name.empty()) { filename = inputPath + opt.Prefix() + finalEmber.m_Name + opt.Suffix() + "." + opt.Format(); } else { ostringstream fnstream; fnstream << inputPath << opt.Prefix() << setfill('0') << setw(padding) << i << opt.Suffix() << "." << opt.Format(); filename = fnstream.str(); } //TotalIterCount() is actually using ScaledQuality() which does not get reset upon ember assignment, //so it ends up using the correct value for quality * strips. iterCount = renderer->TotalIterCount(1); comments = renderer->ImageComments(stats, opt.PrintEditDepth(), opt.IntPalette(), opt.HexPalette()); os.str(""); os << comments.m_NumIters << " / " << iterCount << " (" << std::fixed << std::setprecision(2) << ((double(stats.m_Iters) / double(iterCount)) * 100) << "%)"; VerbosePrint("\nIters ran/requested: " + os.str()); if (!opt.EmberCL()) VerbosePrint("Bad values: " << stats.m_Badvals); VerbosePrint("Render time: " + t.Format(stats.m_RenderMs)); VerbosePrint("Pure iter time: " + t.Format(stats.m_IterMs)); VerbosePrint("Iters/sec: " << size_t(stats.m_Iters / (stats.m_IterMs / 1000.0)) << endl); VerbosePrint("Writing " + filename); if ((opt.Format() == "jpg" || opt.Format() == "bmp") && renderer->NumChannels() == 4) RgbaToRgb(finalImage, finalImage, renderer->FinalRasW(), renderer->FinalRasH()); finalImagep = finalImage.data(); writeSuccess = false; if (opt.Format() == "png") writeSuccess = WritePng(filename.c_str(), finalImagep, finalEmber.m_FinalRasW, finalEmber.m_FinalRasH, opt.BitsPerChannel() / 8, opt.PngComments(), comments, opt.Id(), opt.Url(), opt.Nick()); else if (opt.Format() == "jpg") writeSuccess = WriteJpeg(filename.c_str(), finalImagep, finalEmber.m_FinalRasW, finalEmber.m_FinalRasH, int(opt.JpegQuality()), opt.JpegComments(), comments, opt.Id(), opt.Url(), opt.Nick()); else if (opt.Format() == "ppm") writeSuccess = WritePpm(filename.c_str(), finalImagep, finalEmber.m_FinalRasW, finalEmber.m_FinalRasH); else if (opt.Format() == "bmp") writeSuccess = WriteBmp(filename.c_str(), finalImagep, finalEmber.m_FinalRasW, finalEmber.m_FinalRasH); if (!writeSuccess) cout << "Error writing " << filename << endl; }); if (opt.EmberCL() && opt.DumpKernel()) { if (auto rendererCL = dynamic_cast<RendererCL<T, float>*>(renderer.get())) { cout << "Iteration kernel:\n" << rendererCL->IterKernel() << "\n\n" << "Density filter kernel:\n" << rendererCL->DEKernel() << "\n\n" << "Final accumulation kernel:\n" << rendererCL->FinalAccumKernel() << endl; } } VerbosePrint("Done."); } t.Toc("\nFinished in: ", true); return true; }
TEXLoader::IoStatus TEXLoader::Save(const Texture& texture, bool compressed) { if (texture.GetColorMap(0) == 0) { return kStatusMissingColormapError; } // Calculate log2 of width and height. int width = 0; int height = 0; int log2_width = 0; int log2_height = 0; if (texture.IsCubeMap() == false) { width = texture.GetColorMap(0)->GetWidth(); height = texture.GetColorMap(0)->GetHeight(); } else { width = texture.GetCubeMapPosX(0)->GetWidth(); height = texture.GetCubeMapPosX(0)->GetHeight(); } while ((1 << log2_width) < width) { log2_width++; } while ((1 << log2_height) < height) { log2_height++; } // Prepare file header. FileHeader file_header; file_header.tex_magic_[0] = 'T'; file_header.tex_magic_[1] = 'T'; file_header.tex_magic_[2] = 'E'; file_header.tex_magic_[3] = 'X'; file_header.version_ = 1; file_header.data_offset_ = 16; // Size of file header. file_header.dimension_powers_ = (uint8)((log2_height << 4) | log2_width); file_header.compression_flag_ = compressed == true ? 1 : 0; file_header.map_flags_ = 0; if (texture.IsCubeMap() == false) { if (texture.GetAlphaMap(0) != 0 || texture.GetColorMap(0)->GetBitDepth() == Canvas::kBitdepth32Bit) file_header.map_flags_ |= kAlphaMap; if (texture.GetNormalMap(0) != 0) file_header.map_flags_ |= kNormalMap; if (texture.GetSpecularMap(0) != 0) file_header.map_flags_ |= kSpecularMap; } else { file_header.map_flags_ = kCubeMap; } // Write the file header. save_file_->WriteData(file_header.tex_magic_, 4); save_file_->Write(file_header.version_); save_file_->Write(file_header.data_offset_); save_file_->Write(file_header.dimension_powers_); save_file_->Write(file_header.compression_flag_); save_file_->Write(file_header.map_flags_); int num_levels = texture.GetNumMipMapLevels(); if (texture.IsCubeMap() == false) { int i; for (i = 0; i < num_levels; i++) { if (texture.GetColorMap(i)->GetBitDepth() != Canvas::kBitdepth24Bit) { Canvas temp(*texture.GetColorMap(i), true); temp.ConvertBitDepth(Canvas::kBitdepth24Bit); if (i < (num_levels - 2) && compressed == true) WriteJpeg(temp); else save_file_->WriteData(temp.GetBuffer(), temp.GetBufferByteSize()); } else { if (i < (num_levels - 2) && compressed == true) WriteJpeg(*texture.GetColorMap(i)); else save_file_->WriteData(texture.GetColorMap(i)->GetBuffer(), texture.GetColorMap(i)->GetBufferByteSize()); } } if (texture.GetAlphaMap(0) != 0) { for (i = 0; i < texture.GetNumMipMapLevels(); i++) { if (i < (num_levels - 2) && compressed == true) WriteJpeg(*texture.GetAlphaMap(i)); else save_file_->WriteData(texture.GetAlphaMap(i)->GetBuffer(), texture.GetAlphaMap(i)->GetBufferByteSize()); } } else if(texture.GetColorMap(0)->GetBitDepth() == Canvas::kBitdepth32Bit) { for (i = 0; i < texture.GetNumMipMapLevels(); i++) { Canvas alpha_map; texture.GetColorMap(i)->GetAlphaChannel(alpha_map); if (i < (num_levels - 2) && compressed == true) WriteJpeg(alpha_map); else save_file_->WriteData(alpha_map.GetBuffer(), alpha_map.GetBufferByteSize()); } } if (texture.GetNormalMap(0) != 0) { for (i = 0; i < texture.GetNumMipMapLevels(); i++) { if (i < (num_levels - 2) && compressed == true) WriteJpeg(*texture.GetNormalMap(i)); else save_file_->WriteData(texture.GetNormalMap(i)->GetBuffer(), texture.GetNormalMap(i)->GetBufferByteSize()); } } if (texture.GetSpecularMap(0) != 0) { for (i = 0; i < texture.GetNumMipMapLevels(); i++) { if (i < (num_levels - 2) && compressed == true) WriteJpeg(*texture.GetSpecularMap(i)); else save_file_->WriteData(texture.GetSpecularMap(i)->GetBuffer(), texture.GetSpecularMap(i)->GetBufferByteSize()); } } } else { int i; for (i = 0; i < texture.GetNumMipMapLevels(); i++) { if (i < (num_levels - 2) && compressed == true) WriteJpeg(*texture.GetCubeMapPosX(i)); else save_file_->WriteData(texture.GetCubeMapPosX(i)->GetBuffer(), texture.GetCubeMapPosX(i)->GetBufferByteSize()); } for (i = 0; i < texture.GetNumMipMapLevels(); i++) { if (i < (num_levels - 2) && compressed == true) WriteJpeg(*texture.GetCubeMapNegX(i)); else save_file_->WriteData(texture.GetCubeMapNegX(i)->GetBuffer(), texture.GetCubeMapNegX(i)->GetBufferByteSize()); } for (i = 0; i < texture.GetNumMipMapLevels(); i++) { if (i < (num_levels - 2) && compressed == true) WriteJpeg(*texture.GetCubeMapPosY(i)); else save_file_->WriteData(texture.GetCubeMapPosY(i)->GetBuffer(), texture.GetCubeMapPosY(i)->GetBufferByteSize()); } for (i = 0; i < texture.GetNumMipMapLevels(); i++) { if (i < (num_levels - 2) && compressed == true) WriteJpeg(*texture.GetCubeMapNegY(i)); else save_file_->WriteData(texture.GetCubeMapNegY(i)->GetBuffer(), texture.GetCubeMapNegY(i)->GetBufferByteSize()); } for (i = 0; i < texture.GetNumMipMapLevels(); i++) { if (i < (num_levels - 2) && compressed == true) WriteJpeg(*texture.GetCubeMapPosZ(i)); else save_file_->WriteData(texture.GetCubeMapPosZ(i)->GetBuffer(), texture.GetCubeMapPosZ(i)->GetBufferByteSize()); } for (i = 0; i < texture.GetNumMipMapLevels(); i++) { if (i < (num_levels - 2) && compressed == true) WriteJpeg(*texture.GetCubeMapNegZ(i)); else save_file_->WriteData(texture.GetCubeMapNegZ(i)->GetBuffer(), texture.GetCubeMapNegZ(i)->GetBufferByteSize()); } } save_file_->Close(); return kStatusSuccess; }