/// <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);
	}
}
Beispiel #2
0
void ConvertGbaToPng(char *inputPath, char *outputPath, int width, int bitDepth, char *paletteFilePath, bool hasTransparency)
{
	struct Image image;

	if (paletteFilePath != NULL) {
		ReadGbaPalette(paletteFilePath, &image.palette);
		image.hasPalette = true;
	} else {
		image.hasPalette = false;
	}

	ReadImage(inputPath, width, bitDepth, &image, !image.hasPalette);

	image.hasTransparency = hasTransparency;

	WritePng(outputPath, &image);

	FreeImage(&image);
}
Beispiel #3
0
int WriteImage(const char * filename,
              const std::vector<unsigned char> & ptr,
              int w,
              int h,
              int depth){
  Format f = GetFormat(filename);

  switch (f) {
    case Pnm:
      return WritePnm(filename, ptr, w, h, depth);
    case Png:
      return WritePng(filename, ptr, w, h, depth);
    case Jpg:
      return WriteJpg(filename, ptr, w, h, depth);
    case Tiff:
      return WriteTiff(filename, ptr, w, h, depth);
    default:
      return 0;
  };
}
Beispiel #4
0
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;
}