/* * OpenClonk, http://www.openclonk.org * * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/ * Copyright (c) 2011-2016, The OpenClonk Team and contributors * * Distributed under the terms of the ISC license; see accompanying file * "COPYING" for details. * * "Clonk" is a registered trademark of Matthes Bender, used with permission. * See accompanying file "TRADEMARK" for details. * * To redistribute this file separately, substitute the full license texts * for the above references. */ // png file reading functionality #include "C4Include.h" #include "graphics/StdPNG.h" #include "lib/StdColors.h" #include "platform/StdScheduler.h" // png reading proc void PNGAPI CPNGFile::CPNGReadFn(png_structp png_ptr, png_bytep data, size_t length) { static_cast(png_get_io_ptr(png_ptr))->Read(data, length); } void CPNGFile::Read(unsigned char *pData, int iLength) { // fixme: overflow check schould be done here // simply copy into buffer memcpy(pData, pFilePtr, iLength); // advance file ptr pFilePtr+=iLength; } bool CPNGFile::DoLoad() { // reset file ptr pFilePtr=pFile; // check file if (png_sig_cmp((unsigned char *) pFilePtr, 0, 8)) return false; // setup png for reading fWriteMode=false; png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (!png_ptr) return false; info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) return false; end_info = png_create_info_struct(png_ptr); if (!end_info) return false; // error handling if (setjmp(png_jmpbuf(png_ptr))) return false; // set file-reading proc png_set_read_fn(png_ptr, this, &CPNGFile::CPNGReadFn); // read info png_read_info(png_ptr, info_ptr); // assign local vars png_uint_32 uWdt = iWdt, uHgt = iHgt; png_get_IHDR(png_ptr, info_ptr, &uWdt, &uHgt, &iBPC, &iClrType, &iIntrlcType, &iCmprType, &iFltrType); // convert to bgra if (iClrType == PNG_COLOR_TYPE_PALETTE && iBPC <= 8) png_set_expand(png_ptr); if (iClrType == PNG_COLOR_TYPE_GRAY && iBPC < 8) png_set_expand(png_ptr); if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) png_set_expand(png_ptr); if (iBPC == 16) png_set_strip_16(png_ptr); if (iBPC < 8) png_set_packing(png_ptr); if (iClrType == PNG_COLOR_TYPE_GRAY || iClrType == PNG_COLOR_TYPE_GRAY_ALPHA) png_set_gray_to_rgb(png_ptr); png_set_bgr(png_ptr); // update info png_read_update_info(png_ptr, info_ptr); png_get_IHDR(png_ptr, info_ptr, &uWdt, &uHgt, &iBPC, &iClrType, &iIntrlcType, &iCmprType, &iFltrType); iWdt = uWdt; iHgt = uHgt; // get bit depth/check format switch (iClrType) { case PNG_COLOR_TYPE_RGB: iPixSize=3; break; case PNG_COLOR_TYPE_RGB_ALPHA: iPixSize=4; break; default: return false; // unrecognized image } // allocate mem for the whole image iRowSize=png_get_rowbytes(png_ptr, info_ptr); pImageData = new unsigned char[iRowSize*iHgt]; // create row ptr buffer unsigned char **ppRowBuf = new unsigned char *[iHgt]; unsigned char **ppRows=ppRowBuf; unsigned char *pRow=pImageData; for (unsigned int i=0; ipFile = pFile; iFileSize=iSize; fpFileOwned=false; // perform the loading if (!DoLoad()) { Clear(); return false; } // reset file-field this->pFile = nullptr; iFileSize=0; // success return true; } DWORD CPNGFile::GetPix(int iX, int iY) { // image loaded? if (!pImageData) return 0; // return pixel value unsigned char *pPix=pImageData+iY*iRowSize+iX*iPixSize; switch (iClrType) { case PNG_COLOR_TYPE_RGB: return C4RGB(pPix[2], pPix[1], pPix[0]); case PNG_COLOR_TYPE_RGB_ALPHA: return RGBA(pPix[2], pPix[1], pPix[0], pPix[3]); } return 0; } bool CPNGFile::Create(int iWdt, int iHgt, bool fAlpha) { // catch invalid size if (iWdt<=0 || iHgt<=0) return false; // clear current image in mem Clear(); // set dimensions this->iWdt=iWdt; this->iHgt=iHgt; // set color type iBPC = 8; iClrType = fAlpha ? PNG_COLOR_TYPE_RGB_ALPHA : PNG_COLOR_TYPE_RGB; iPixSize = fAlpha ? 4 : 3; // set interlacing, filters and stuff iIntrlcType = PNG_INTERLACE_NONE; iCmprType = PNG_COMPRESSION_TYPE_DEFAULT; iFltrType = PNG_FILTER_TYPE_DEFAULT; // calculate rowbytes int iBPP = (fAlpha ? 4 : 3) * iBPC; iRowSize = (iBPP*iWdt+7)>>3; // create image data pImageData = new unsigned char[iHgt * iRowSize]; // success return true; } bool CPNGFile::SetPix(int iX, int iY, DWORD dwValue) { // image created? if (!pImageData) return false; // set pixel value unsigned char *pPix=pImageData+iY*iRowSize+iX*iPixSize; switch (iClrType) { case PNG_COLOR_TYPE_RGB: // RGB: set r, g and b values pPix[0] = GetBlueValue(dwValue); pPix[1] = GetGreenValue(dwValue); pPix[2] = GetRedValue(dwValue); return true; case PNG_COLOR_TYPE_RGB_ALPHA: // RGBA: simply set in mem *(DWORD *) pPix = dwValue; return true; } return false; } bool CPNGFile::Save(const char *szFilename) { // regular file saving - first, there has to be a buffer if (!pImageData) return false; // open the file fp = fopen(szFilename, "wb"); if (!fp) return false; // clear any previously initialized png-structs (e.g. by reading) ClearPngStructs(); // reinit them for writing fWriteMode=true; png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (!png_ptr) { Clear(); return false; } info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { Clear(); return false; } // error handling if (setjmp(png_jmpbuf(png_ptr))) { Clear(); return false; } // io initialization png_init_io(png_ptr, fp); // compression stuff png_set_filter(png_ptr, 0, PNG_FILTER_NONE | PNG_FILTER_SUB | PNG_FILTER_PAETH); png_set_compression_level(png_ptr, Z_BEST_COMPRESSION); png_set_compression_mem_level(png_ptr, 8); png_set_compression_strategy(png_ptr, Z_DEFAULT_STRATEGY); png_set_compression_window_bits(png_ptr, 15); png_set_compression_method(png_ptr, 8); // set header png_set_IHDR(png_ptr, info_ptr, iWdt, iHgt, iBPC, iClrType, iIntrlcType, iCmprType, iFltrType); // double-check our calculated row size int iRealRowSize=png_get_rowbytes(png_ptr, info_ptr); if (iRealRowSize != iRowSize) { // this won't go well, so better abort Clear(); return false; } // write png header png_write_info(png_ptr, info_ptr); // image data is given as bgr... png_set_bgr(png_ptr); // create row array unsigned char **ppRowBuf = new unsigned char *[iHgt]; unsigned char **ppRows=ppRowBuf; unsigned char *pRow=pImageData; for (unsigned int i=0; i png; StdCopyStrBuf filename; static CStdCSec threads_sec; static std::list threads; public: CPNGSaveThread(CPNGFile *png, const char *filename); virtual ~CPNGSaveThread(); static bool HasPendingThreads(); protected: virtual void Execute(); virtual bool IsSelfDestruct() { return true; } }; CStdCSec CPNGSaveThread::threads_sec; std::list CPNGSaveThread::threads; CPNGSaveThread::CPNGSaveThread(CPNGFile *png, const char *filename) : png(png), filename(filename) { // keep track of current saves CStdLock lock(&threads_sec); threads.push_back(this); } CPNGSaveThread::~CPNGSaveThread() { // keep track of current saves CStdLock lock(&threads_sec); threads.remove(this); } void CPNGSaveThread::Execute() { // Save without feedback. There's no way to post e.g. a log message to the main thread at the moment. // But if saving fails, there's just a missing screenshot, which shouldn't be a big deal. png->Save(filename.getData()); SignalStop(); } bool CPNGSaveThread::HasPendingThreads() { CStdLock lock(&threads_sec); return !threads.empty(); } void CPNGFile::ScheduleSaving(CPNGFile *png, const char *filename) { // start a background thread to save the png file // thread is responsible for cleaning up CPNGSaveThread *saver = new CPNGSaveThread(png, filename); if (!saver->Start()) delete saver; } void CPNGFile::WaitForSaves() { // Yield main thread until all pending saves have finished.Wait for bool first = true; while (CPNGSaveThread::HasPendingThreads()) { // English message because localization data is no longer loaded if (first) LogSilent("Waiting for pending image files to be written to disc..."); first = false; #ifdef HAVE_WINTHREAD Sleep(100); #else sched_yield(); #endif } }