diff --git a/.travis.yml b/.travis.yml index 103f3f3eb..26a8c6541 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ before_install: - echo yes | sudo apt-add-repository ppa:ubuntu-sdk-team/ppa - sudo apt-get update -qq - sudo apt-get install -qq libboost1.54-all-dev libsdl2-dev libsdl2-image-dev libsdl-ttf2.0-dev zlib1g-dev - libbz2-dev doxygen libfreetype6-dev libpython2.7 libsdl2-mixer-dev cmake qtdeclarative5-dev + libbz2-dev doxygen libfreetype6-dev libpython2.7 libsdl2-mixer-dev cmake qtdeclarative5-dev libpng12-dev zlib1g-dev - wget http://wheybags.netsoc.ie/pkg/librocket_1.3-1_amd64.deb - sudo dpkg -i librocket_1.3-1_amd64.deb - wget http://wheybags.netsoc.ie/pkg/librocket-dev_1.3-1_amd64.deb diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f96522b4..21c210087 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,8 @@ endif() Find_Package(SDL2 REQUIRED) Find_Package(SDL2_image REQUIRED) Find_Package(SDL2_mixer REQUIRED) +Find_Package(ZLIB REQUIRED) +find_package(PNG REQUIRED) Find_Package(OpenGL REQUIRED) Find_Package(PythonLibs 2 REQUIRED) @@ -56,6 +58,7 @@ include_directories ( ${SDL2IMAGE_INCLUDE_DIR} ${SDL2MIXER_INCLUDE_DIR} ${OPENGL_INCLUDE_DIRS} + ${PNG_INCLUDE_DIR} ${Boost_INCLUDE_DIR} ${PYTHON_INCLUDE_DIR} ${ROCKET_INCLUDE_DIR} @@ -80,6 +83,7 @@ link_libraries ( ${SDL2_LIBRARY} ${SDL2IMAGE_LIBRARY} ${SDL2MIXER_LIBRARY} + ${PNG_LIBRARY} ${OPENGL_LIBRARIES} ${ROCKET_LIBRARIES} ${PYTHON_LIBRARIES} diff --git a/apps/celview/CMakeLists.txt b/apps/celview/CMakeLists.txt index ab0aed6f0..34c99c34a 100644 --- a/apps/celview/CMakeLists.txt +++ b/apps/celview/CMakeLists.txt @@ -1,2 +1,26 @@ -add_executable(celview main.cpp) +set(CMAKE_AUTOMOC ON) +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +qt5_wrap_ui(UI_HEADERS ../../resources/ui/celview.ui) + +include_directories ( + ${SDL2_INCLUDE_DIR}) + +link_libraries ( + StormLib + ${SDL2_LIBRARY}) + +add_executable(celview + + ../../resources/ui/celview.ui + main.cpp + mainwindow.h + mainwindow.cpp +) + set_target_properties(celview PROPERTIES COMPILE_FLAGS "${FA_COMPILER_FLAGS}") + +if(UNIX) # qt headers generate a bunch of warnings, just ignore em + set_target_properties(StormLib PROPERTIES COMPILE_FLAGS "-w") +endif() + diff --git a/apps/celview/main.cpp b/apps/celview/main.cpp index 0222c095a..b48f94ec8 100644 --- a/apps/celview/main.cpp +++ b/apps/celview/main.cpp @@ -1,57 +1,11 @@ -#include +#include "mainwindow.h" +#include -#include -#include -#include - -bool done = false; -size_t celIndex = 0; -size_t max; -void keyPress(Input::Key key) -{ - switch(key) - { - case Input::KEY_UP: - if(celIndex < max-1) - celIndex++; - break; - case Input::KEY_DOWN: - if(celIndex > 0) - celIndex--; - break; - case Input::KEY_q: - done = true; - break; - default: - break; - } - - std::cout << "frame " << celIndex+1 << "/" << max << std::endl; -} - -int main(int, char** argv) +int main(int argc, char *argv[]) { - FAIO::init(); - Render::RenderSettings settings; - settings.windowWidth = 1280; - settings.windowHeight = 960; - Render::init(settings); - - Input::InputManager input(&keyPress, NULL, NULL, NULL, NULL, NULL); - - Render::SpriteGroup cel(argv[1]); - max = cel.size(); - - while(!done) - { - input.poll(); - input.processInput(false); - - Render::clear(); - Render::drawAt(cel[celIndex], 0, 0); - Render::draw(); - } + QApplication a(argc, argv); + MainWindow w; + w.show(); - FAIO::quit(); - return 0; + return a.exec(); } diff --git a/apps/celview/mainwindow.cpp b/apps/celview/mainwindow.cpp new file mode 100644 index 000000000..be1a75c58 --- /dev/null +++ b/apps/celview/mainwindow.cpp @@ -0,0 +1,345 @@ +#include "mainwindow.h" +#include "ui_celview.h" +#include "render/render.h" + +#include +#include +#include +#include +#include +#include + + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::MainWindow), + mDiabdat(NULL), + mIsAnimation(false), + mSettingsFile(QApplication::applicationDirPath() + "/celview.ini"), + mSettings(mSettingsFile, QSettings::IniFormat) +{ + ui->setupUi(this); + this->setWindowTitle("Celview"); + connect(ui->listView, SIGNAL(itemDoubleClicked(QListWidgetItem *)), this, SLOT(itemDoubleClicked(QListWidgetItem * ))); + connect(&mRenderTimer, SIGNAL(timeout()), this, SLOT(updateRender())); + + ui->currentFrame->setValidator(new QIntValidator(0, 9999999, this)); + ui->currentFrame->setText("0"); + + qDebug() << mSettingsFile; + + loadSettings(); + initRender(); +} + +MainWindow::~MainWindow() +{ + closeMPQ(); + saveSettings(); + delete ui; +} + +void MainWindow::initRender() +{ + Render::init(mRenderSettings); +} + +void MainWindow::loadSettings() +{ + mFilename = mSettings.value("filename", "DIABLO.MPQ").toString(); + mListfile = mSettings.value("listfile", "Diablo I").toString(); + mRenderSettings.windowWidth = mSettings.value("windowWidth", "800").toInt(); + mRenderSettings.windowHeight = mSettings.value("windowHeight", "600").toInt(); + mBackgroundColor = QColor(mSettings.value("backgroundColor", "#0000FF").toString()); + + ui->lineEdit->setText(mFilename); + ui->lineEdit_2->setText(mListfile); + + if (!QFile(mSettingsFile).exists()) + { + saveSettings(); + } +} + +void MainWindow::saveSettings() +{ + mSettings.setValue("filename", mFilename); + mSettings.setValue("listfile", mListfile); + + Render::RenderSettings windowSize = Render::getWindowSize(); + + mSettings.setValue("windowWidth", windowSize.windowWidth); + mSettings.setValue("windowHeight", windowSize.windowHeight); + + mSettings.setValue("backgroundColor", mBackgroundColor.name()); + mSettings.sync(); +} + +void MainWindow::itemDoubleClicked(QListWidgetItem* item) +{ + mRenderTimer.stop(); + mCurrentCelFilename = ui->listView->currentItem()->text(); + mCurrentCel = QSharedPointer(new Render::SpriteGroup(mCurrentCelFilename.toStdString().c_str())); + if(mCurrentCel->size() == 0) + QMessageBox::critical(0,"Error","CEL/CL2 file can't be loaded"); + + mCurrentFrame = 0; + ui->numFramesLabel->setText(QString("Number of frames: ") + QString::number(mCurrentCel->size())); + ui->currentFrame->setText("0"); + this->setWindowTitle(QString("Celview - ") + mCurrentCelFilename); + + mRenderTimer.start(200); +} + +void MainWindow::on_actionSet_background_color_2_triggered() +{ + QColorDialog dialog; + dialog.setCurrentColor(mBackgroundColor); + int result = dialog.exec(); + + if (result) + { + mBackgroundColor = dialog.currentColor(); + } +} + +void MainWindow::on_actionExit_triggered() +{ + this->close(); +} + +void MainWindow::on_selectMPQ_clicked() +{ + QString tmpFilename = QFileDialog::getOpenFileName(this, tr("Open MPQ"), ".", tr("MPQ Files (*.mpq)")); + if (!tmpFilename.isEmpty()) + { + mFilename = tmpFilename; + ui->lineEdit->setText(mFilename); + } +} + +void MainWindow::on_selectFileList_clicked() +{ + QString tmpFilename = QFileDialog::getOpenFileName(this, tr("Open Listfile"), ".", tr("List Files (*.txt)")); + if (!tmpFilename.isEmpty()) + { + mListfile = tmpFilename; + ui->lineEdit_2->setText(mListfile); + } +} + +void MainWindow::on_openButton_clicked() +{ + closeMPQ(); + if (openMPQ() == false) return; + listFiles(); +} + + +void MainWindow::on_leftButton_clicked() +{ + if (!mCurrentCel) + return; + + mCurrentFrame--; + if (mCurrentFrame < 0) + { + mCurrentFrame = mCurrentCel->size() - 1; + } + + ui->currentFrame->setText(QString::number(mCurrentFrame)); +} + +void MainWindow::on_rightButton_clicked() +{ + if (!mCurrentCel) + return; + + mCurrentFrame++; + if (mCurrentFrame >= (int)mCurrentCel->size()) + mCurrentFrame = 0; + + ui->currentFrame->setText(QString::number(mCurrentFrame)); +} + +void MainWindow::on_actionExport_CEL_CL2_to_PNG_triggered() +{ + if(mDiabdat == NULL) + { + QMessageBox::critical(0, "Error", "Open MPQ archive!"); + return; + } + + if(mCurrentCelFilename.isEmpty()) + { + QMessageBox::critical(0, "Error", "Open CEL/CL2 file!"); + return; + } + + QString tmpFilename = QFileDialog::getSaveFileName(this, tr("Save CEL/CL2 as PNG"), ".", tr("PNG Files (*.png)")); + if (!tmpFilename.isEmpty()) + { + Render::SpriteGroup::toPng(mCurrentCelFilename.toStdString(), tmpFilename.toStdString()); + QMessageBox::information(0, "Success", "Export complete!"); + } +} + +void MainWindow::on_actionExport_all_CEL_CL2_to_PNG_triggered() +{ + if(mDiabdat == NULL) + { + QMessageBox::critical(0, "Error", "Open MPQ archive!"); + return; + } + + QString dir = QFileDialog::getExistingDirectory(this, tr("Open Directory"),"",QFileDialog::ShowDirsOnly|QFileDialog::DontResolveSymlinks); + if(!dir.isEmpty()) + { + int count = ui->listView->count(); + for(int i = 0 ; i < count ; i++) + { + QString pathInMPQ = ui->listView->item(i)->text(); + QString modifiedPathInMPQ = pathInMPQ; + modifiedPathInMPQ = modifiedPathInMPQ.replace('\\', '_').replace('/','_').replace(".cel", ".png").replace(".cl2",".png"); + + std::string stdModifiedPathInMPQ = modifiedPathInMPQ.toStdString(); + std::string path = pathInMPQ.toStdString(); + std::string target = dir.toStdString() + "/" + stdModifiedPathInMPQ; + + Render::SpriteGroup::toPng(path, target); + } + + QMessageBox::information(0, "Success", "Export complete!"); + } +} + +void MainWindow::on_startStopButton_clicked() +{ + mIsAnimation = !mIsAnimation; +} + +void MainWindow::on_currentFrame_textEdited(const QString &arg1) +{ + if (!mCurrentCel) + return; + + int tmpCurrentFrame = arg1.toInt(); + if (tmpCurrentFrame < 0 || tmpCurrentFrame >= (int)mCurrentCel->size()) + return; + + mCurrentFrame = tmpCurrentFrame; +} + +void MainWindow::updateRender() +{ + int size = mCurrentCel->size(); + + if (mIsAnimation) + { + mCurrentFrame = ++mCurrentFrame % size; + ui->currentFrame->setText(QString::number(mCurrentFrame)); + } + + Render::clear(mBackgroundColor.red(), mBackgroundColor.green(), mBackgroundColor.blue()); + + if(mCurrentCel->size() > 0) + Render::drawAt((*mCurrentCel)[mCurrentFrame], 0, 0); + Render::draw(); +} + +bool MainWindow::openMPQ() +{ + mFilename = ui->lineEdit->text(); + mListfile = ui->lineEdit_2->text(); + + if (mListfile.isEmpty() || mFilename.isEmpty()) + { + QMessageBox::critical(0, "Error", "Select MPQ archive and file list!"); + return false; + } + + if (!fileExists(mFilename)) + { + QMessageBox::critical(0, "Error", "MPQ archive does not exist!"); + return false; + } + + if (!fileExists(mListfile)) + { + QMessageBox::critical(0, "Error", "Listfile does not exist!"); + return false; + } + + const bool success = SFileOpenArchive(mFilename.toStdString().c_str(), 0, STREAM_FLAG_READ_ONLY, &mDiabdat); + + if (!success) + { + QMessageBox::critical(0, "Error", "Cannot open file \"" + mFilename + "\""); + return false; + } + + std::string fileList = ui->lineEdit_2->text().toStdString(); + SFileAddListFile(mDiabdat, fileList.c_str()); + + FAIO::init(mFilename.toStdString().c_str()); + + return true; +} + +void MainWindow::closeMPQ() +{ + FAIO::quit(); + + ui->listView->clear(); + + if (mDiabdat) + { + SFileCloseArchive(mDiabdat); + mDiabdat = NULL; + } +} + +void MainWindow::listFiles() +{ + QStringList fileList; + listFilesDetails("cel", fileList); + listFilesDetails("cl2", fileList); + + fileList.sort(); + + for (QStringList::const_iterator it = fileList.cbegin(); it != fileList.cend(); it++) + { + ui->listView->addItem(*it); + } +} + +void MainWindow::listFilesDetails(QString extension, QStringList & list) +{ + extension = "*." + extension; + + SFILE_FIND_DATA findFileData; + HANDLE findHandle = SFileFindFirstFile(mDiabdat, extension.toStdString().c_str(), &findFileData, NULL); + + list.append(QString(findFileData.cFileName)); + + while (SFileFindNextFile(findHandle, &findFileData)) + { + list.append(QString(findFileData.cFileName)); + } + + SFileFindClose(findHandle); +} + +bool MainWindow::fileExists(QString path) +{ + QFileInfo checkFile(path); + + if (checkFile.exists() && checkFile.isFile()) + { + return true; + } + else + { + return false; + } +} \ No newline at end of file diff --git a/apps/celview/mainwindow.h b/apps/celview/mainwindow.h new file mode 100644 index 000000000..ce8763afb --- /dev/null +++ b/apps/celview/mainwindow.h @@ -0,0 +1,84 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace Ui { +class MainWindow; +} + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = 0); + ~MainWindow(); + +private: + void initRender(); + void loadSettings(); + void saveSettings(); + bool openMPQ(); + void closeMPQ(); + void listFiles(); + void listFilesDetails(QString extension, QStringList & list); + bool fileExists(QString path); + +private slots: + + void on_actionExit_triggered(); + + void on_selectMPQ_clicked(); + + void on_selectFileList_clicked(); + + void on_openButton_clicked(); + + void on_leftButton_clicked(); + + void on_rightButton_clicked(); + + void on_startStopButton_clicked(); + + void on_currentFrame_textEdited(const QString &); + + void on_actionSet_background_color_2_triggered(); + + void on_actionExport_CEL_CL2_to_PNG_triggered(); + + void on_actionExport_all_CEL_CL2_to_PNG_triggered(); + + void itemDoubleClicked(QListWidgetItem *); + + void updateRender(); + +private: + HANDLE mDiabdat ; + QString mFilename; + QString mListfile; + QString mCurrentCelFilename; + const QString mSettingsFile; + QColor mBackgroundColor; + QSettings mSettings; + int mCurrentFrame; + QSharedPointer mCurrentCel; + Render::RenderSettings mRenderSettings; + QTimer mRenderTimer; + bool mIsAnimation; + Ui::MainWindow *ui; +}; + +#endif // MAINWINDOW_H diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index dba9fa875..d41e26980 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -35,7 +35,10 @@ add_library(Misc misc/misc.cpp misc/disablewarn.h misc/enablewarn.h + misc/savePNG.h + misc/savePNG.cpp ) +target_link_libraries(Misc ${PNG_LIBRARY}) SET_TARGET_PROPERTIES(Misc PROPERTIES LINKER_LANGUAGE CXX) set_target_properties(Misc PROPERTIES COMPILE_FLAGS "${FA_COMPILER_FLAGS}") diff --git a/components/cel/celdecoding.cpp b/components/cel/celdecoding.cpp index 87e95eb14..29f553f13 100644 --- a/components/cel/celdecoding.cpp +++ b/components/cel/celdecoding.cpp @@ -81,7 +81,7 @@ namespace Cel i += val; // Workaround for frames that start with a few px, then trans for the rest of the line - if(128 <= frame[i+1]) + if(i+1 >= frame.size() || 128 <= frame[i+1]) hasTrans = true; } diff --git a/components/cel/celfile.cpp b/components/cel/celfile.cpp index 779dd0e91..39a07c457 100644 --- a/components/cel/celfile.cpp +++ b/components/cel/celfile.cpp @@ -125,17 +125,36 @@ namespace Cel std::vector frameOffsets(numFrames+1); + bool error = false; for(size_t j = 0; j <= numFrames; j++) - FAIO::FAfread(&frameOffsets[j], 4, 1, file); + { + int success = FAIO::FAfread(&frameOffsets[j], 4, 1, file); + + // Dirty hack to prevent further loading cl2 + // that can't be loaded because we don't know how. + if(!success) + { + error = true; + numFrames = 0; + break; + } + } + if(error) + break; FAIO::FAfseek(file, headerOffsets[i]+ frameOffsets[0], SEEK_SET); for(size_t j = 0; j < numFrames; j++) { - mFrames.push_back(std::vector(frameOffsets[j+1]-frameOffsets[j])); - FAIO::FAfread(&mFrames[mFrames.size()-1][0], 1, frameOffsets[j+1]-frameOffsets[j], file); + int diff = frameOffsets[j+1]-frameOffsets[j]; + + if(diff > 0) + { + mFrames.push_back(std::vector(diff)); + FAIO::FAfread(&mFrames[mFrames.size()-1][0], 1, diff, file); + } } } @@ -168,10 +187,12 @@ namespace Cel Pal CelFile::getPallette(std::string filename) { std::string palFilename; - if(Misc::StringUtils::endsWith(filename, "l1.cel")) + if(Misc::StringUtils::startsWith(filename, "levels") && Misc::StringUtils::endsWith(filename, "l1.cel")) palFilename = Misc::StringUtils::replaceEnd("l1.cel", "l1.pal", filename); - else if(Misc::StringUtils::endsWith(filename, "l2.cel")) + else if (Misc::StringUtils::startsWith(filename, "levels") && Misc::StringUtils::endsWith(filename, "l2.cel")) palFilename = Misc::StringUtils::replaceEnd("l2.cel", "l2.pal", filename); + else if (Misc::StringUtils::startsWith(Misc::StringUtils::lowerCase(filename), "gendata")) + palFilename = Misc::StringUtils::replaceEnd(".cel", ".pal", filename); else palFilename = "levels/towndata/town.pal"; diff --git a/components/faio/faio.cpp b/components/faio/faio.cpp index 04c593277..cdea6a3ac 100644 --- a/components/faio/faio.cpp +++ b/components/faio/faio.cpp @@ -39,13 +39,13 @@ namespace FAIO HANDLE diabdat = NULL; - bool init() + bool init(const std::string pathMPQ) { - const bool success = SFileOpenArchive(getMPQFileName().c_str(), 0, STREAM_FLAG_READ_ONLY, &diabdat); + const bool success = SFileOpenArchive(pathMPQ.c_str(), 0, STREAM_FLAG_READ_ONLY, &diabdat); if (!success) { - std::cerr << "Failed to open " << DIABDAT_MPQ << " with error " << GetLastError() << std::endl; + std::cerr << "Failed to open " << pathMPQ.c_str() << " with error " << GetLastError() << std::endl; } return success; diff --git a/components/faio/faio.h b/components/faio/faio.h index c58b457a5..be5b5e474 100644 --- a/components/faio/faio.h +++ b/components/faio/faio.h @@ -42,7 +42,7 @@ namespace FAIO friend size_t FAsize(FAFile* stream); }; - bool init(); + bool init(const std::string pathMPQ = "DIABDAT.MPQ"); void quit(); FAFile* FAfopen(const std::string& filename); diff --git a/components/misc/savePNG.cpp b/components/misc/savePNG.cpp new file mode 100644 index 000000000..c2ec74984 --- /dev/null +++ b/components/misc/savePNG.cpp @@ -0,0 +1,154 @@ +/* +* SDL_SavePNG -- libpng-based SDL_Surface writer. +* +* This code is free software, available under zlib/libpng license. +* http://www.libpng.org/pub/png/src/libpng-LICENSE.txt +*/ +#include +#include + +#define SUCCESS 0 +#define ERROR -1 + +#define USE_ROW_POINTERS + +#if SDL_BYTEORDER == SDL_BIG_ENDIAN +#define rmask 0xFF000000 +#define gmask 0x00FF0000 +#define bmask 0x0000FF00 +#define amask 0x000000FF +#else +#define rmask 0x000000FF +#define gmask 0x0000FF00 +#define bmask 0x00FF0000 +#define amask 0xFF000000 +#endif + +/* libpng callbacks */ +static void png_error_SDL(png_structp ctx, png_const_charp str) +{ + SDL_SetError("libpng: %s\n", str); +} +static void png_write_SDL(png_structp png_ptr, png_bytep data, png_size_t length) +{ + SDL_RWops *rw = (SDL_RWops*)png_get_io_ptr(png_ptr); + SDL_RWwrite(rw, data, sizeof(png_byte), length); +} + +SDL_Surface *SDL_PNGFormatAlpha(SDL_Surface *src) +{ + SDL_Surface *surf; + SDL_Rect rect = { 0 }; + + /* NO-OP for images < 32bpp and 32bpp images that already have Alpha channel */ + if (src->format->BitsPerPixel <= 24 || src->format->Amask) { + src->refcount++; + return src; + } + + /* Convert 32bpp alpha-less image to 24bpp alpha-less image */ + rect.w = src->w; + rect.h = src->h; + surf = SDL_CreateRGBSurface(src->flags, src->w, src->h, 24, + src->format->Rmask, src->format->Gmask, src->format->Bmask, 0); + SDL_LowerBlit(src, &rect, surf, &rect); + + return surf; +} + +int SDL_SavePNG_RW(SDL_Surface *surface, SDL_RWops *dst, int freedst) +{ + png_structp png_ptr; + png_infop info_ptr; + png_colorp pal_ptr; + SDL_Palette *pal; + int i, colortype; +#ifdef USE_ROW_POINTERS + png_bytep *row_pointers; +#endif + /* Initialize and do basic error checking */ + if (!dst) + { + SDL_SetError("Argument 2 to SDL_SavePNG_RW can't be NULL, expecting SDL_RWops*\n"); + return (ERROR); + } + if (!surface) + { + SDL_SetError("Argument 1 to SDL_SavePNG_RW can't be NULL, expecting SDL_Surface*\n"); + if (freedst) SDL_RWclose(dst); + return (ERROR); + } + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, png_error_SDL, NULL); /* err_ptr, err_fn, warn_fn */ + if (!png_ptr) + { + SDL_SetError("Unable to png_create_write_struct on %s\n", PNG_LIBPNG_VER_STRING); + if (freedst) SDL_RWclose(dst); + return (ERROR); + } + info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) + { + SDL_SetError("Unable to png_create_info_struct\n"); + png_destroy_write_struct(&png_ptr, NULL); + if (freedst) SDL_RWclose(dst); + return (ERROR); + } + if (setjmp(png_jmpbuf(png_ptr))) /* All other errors, see also "png_error_SDL" */ + { + png_destroy_write_struct(&png_ptr, &info_ptr); + if (freedst) SDL_RWclose(dst); + return (ERROR); + } + + /* Setup our RWops writer */ + png_set_write_fn(png_ptr, dst, png_write_SDL, NULL); /* w_ptr, write_fn, flush_fn */ + + /* Prepare chunks */ + colortype = PNG_COLOR_MASK_COLOR; + if (surface->format->BytesPerPixel > 0 + && surface->format->BytesPerPixel <= 8 + && (pal = surface->format->palette)) + { + colortype |= PNG_COLOR_MASK_PALETTE; + pal_ptr = (png_colorp)malloc(pal->ncolors * sizeof(png_color)); + for (i = 0; i < pal->ncolors; i++) { + pal_ptr[i].red = pal->colors[i].r; + pal_ptr[i].green = pal->colors[i].g; + pal_ptr[i].blue = pal->colors[i].b; + } + png_set_PLTE(png_ptr, info_ptr, pal_ptr, pal->ncolors); + free(pal_ptr); + } + else if (surface->format->BytesPerPixel > 3 || surface->format->Amask) + colortype |= PNG_COLOR_MASK_ALPHA; + + png_set_IHDR(png_ptr, info_ptr, surface->w, surface->h, 8, colortype, + PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + + // png_set_packing(png_ptr); + + /* Allow BGR surfaces */ + if (surface->format->Rmask == bmask + && surface->format->Gmask == gmask + && surface->format->Bmask == rmask) + png_set_bgr(png_ptr); + + /* Write everything */ + png_write_info(png_ptr, info_ptr); +#ifdef USE_ROW_POINTERS + row_pointers = (png_bytep*)malloc(sizeof(png_bytep)*surface->h); + for (i = 0; i < surface->h; i++) + row_pointers[i] = (png_bytep)(Uint8*)surface->pixels + i * surface->pitch; + png_write_image(png_ptr, row_pointers); + free(row_pointers); +#else + for (i = 0; i < surface->h; i++) + png_write_row(png_ptr, (png_bytep)(Uint8*)surface->pixels + i * surface->pitch); +#endif + png_write_end(png_ptr, info_ptr); + + /* Done */ + png_destroy_write_struct(&png_ptr, &info_ptr); + if (freedst) SDL_RWclose(dst); + return (SUCCESS); +} \ No newline at end of file diff --git a/components/misc/savePNG.h b/components/misc/savePNG.h new file mode 100644 index 000000000..4375c5047 --- /dev/null +++ b/components/misc/savePNG.h @@ -0,0 +1,36 @@ +#ifndef _SDL_SAVEPNG +#define _SDL_SAVEPNG +/* +* SDL_SavePNG -- libpng-based SDL_Surface writer. +* +* This code is free software, available under zlib/libpng license. +* http://www.libpng.org/pub/png/src/libpng-LICENSE.txt +*/ +#include +/* +* Save an SDL_Surface as a PNG file. +* +* Returns 0 success or -1 on failure, the error message is then retrievable +* via SDL_GetError(). +*/ +#define SDL_SavePNG(surface, file) \ + SDL_SavePNG_RW(surface, SDL_RWFromFile(file, "wb"), 1) + +/* +* Save an SDL_Surface as a PNG file, using writable RWops. +* +* surface - the SDL_Surface structure containing the image to be saved +* dst - a data stream to save to +* freedst - non-zero to close the stream after being written +* +* Returns 0 success or -1 on failure, the error message is then retrievable +* via SDL_GetError(). +*/ +extern int SDL_SavePNG_RW(SDL_Surface *surface, SDL_RWops *rw, int freedst); + +/* +* Return new SDL_Surface with a format suitable for PNG output. +*/ +extern SDL_Surface *SDL_PNGFormatAlpha(SDL_Surface *src); + +#endif \ No newline at end of file diff --git a/components/render/render.h b/components/render/render.h index 7a37dcc56..87a5f2a5b 100644 --- a/components/render/render.h +++ b/components/render/render.h @@ -57,8 +57,8 @@ namespace Render void quit(); - void resize(size_t w, size_t h); - + void resize(size_t w, size_t h); + RenderSettings getWindowSize(); void updateGuiBuffer(std::vector* buffer); void quitGui(); void drawGui(std::vector& buffer, SpriteCacheBase* cache); @@ -94,6 +94,9 @@ namespace Render return mAnimLength; } + static void toPng(const std::string& celPath, const std::string& pngPath); + + private: std::vector mSprites; size_t mAnimLength; @@ -114,7 +117,7 @@ namespace Render std::pair getClickedTile(const Level::Level& level, size_t x, size_t y, int32_t x1, int32_t y1, int32_t x2, int32_t y2, size_t dist); - void clear(); + void clear(int r = 0, int g = 0, int b = 255); } #endif diff --git a/components/render/sdl2backend.cpp b/components/render/sdl2backend.cpp index b7f2b6cbe..9aa308189 100644 --- a/components/render/sdl2backend.cpp +++ b/components/render/sdl2backend.cpp @@ -11,6 +11,7 @@ #include "../level/level.h" #include +#include #include #include @@ -138,7 +139,7 @@ namespace Render } - + void quit() { SDL_DestroyRenderer(renderer); @@ -155,6 +156,13 @@ namespace Render resized = true; } + RenderSettings getWindowSize() + { + RenderSettings settings; + SDL_GetWindowSize(screen, &settings.windowWidth, &settings.windowHeight); + return settings; + } + void updateGuiBuffer(std::vector* buffer) { if(resized) @@ -397,12 +405,46 @@ namespace Render mAnimLength = cel.animLength(); } + void SpriteGroup::toPng(const std::string& celPath, const std::string& pngPath) + { + Cel::CelFile cel(celPath); + + size_t numFrames = cel.animLength(); + if(numFrames == 0) + return; + + int sumWidth = 0; + int maxHeight = 0; + for(size_t i = 0; i < numFrames; i++) + { + sumWidth += cel[i].mWidth; + if(cel[i].mHeight > maxHeight) maxHeight = cel[i].mHeight; + } + + if(sumWidth == 0) + return; + + SDL_Surface* s = createTransparentSurface(sumWidth, maxHeight); + unsigned int x = 0; + unsigned int dx = 0; + for(size_t i = 0; i < numFrames; i++) + { + drawFrame(s, x, 0, cel[i]); + dx = cel[i].mWidth; + x += dx; + } + + SDL_SavePNG(s,pngPath.c_str()); + + SDL_FreeSurface(s); + } + void SpriteGroup::destroy() { for(size_t i = 0; i < mSprites.size(); i++) SDL_DestroyTexture((SDL_Texture*)mSprites[i]); } - + void drawMinPillarTop(SDL_Surface* s, int x, int y, const std::vector& pillar, Cel::CelFile& tileset); void drawMinPillarBase(SDL_Surface* s, int x, int y, const std::vector& pillar, Cel::CelFile& tileset); @@ -440,9 +482,9 @@ namespace Render h = tmpH; } - void clear() + void clear(int r, int g, int b) { - SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255); + SDL_SetRenderDrawColor(renderer, r, g, b, 255); SDL_RenderClear(renderer); } diff --git a/resources/ui/celview.ui b/resources/ui/celview.ui new file mode 100644 index 000000000..06ede7936 --- /dev/null +++ b/resources/ui/celview.ui @@ -0,0 +1,214 @@ + + + MainWindow + + + + 0 + 0 + 587 + 417 + + + + MainWindow + + + + + + + 0 + + + + + Select file list... + + + + + + + Select MPQ... + + + + + + + + + + + + + + + Open archive + + + + + + + QLayout::SetDefaultConstraint + + + + + + + + + + 0 + + + + + + + Controls + + + + + + + 0 + 0 + + + + Number of frames: 0 + + + + + + + + 0 + 0 + + + + Start/Stop animation + + + + + + + + 0 + 0 + + + + + 50 + 16777215 + + + + < + + + + + + + + 0 + 0 + + + + + 100 + 16777215 + + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + 50 + 16777215 + + + + > + + + + + + + + + + + + + 0 + 0 + 587 + 21 + + + + + File + + + + + + + + Edit + + + + + + + + + Exit + + + + + Set background color + + + + + Export CEL/CL2 to PNG + + + + + Export all CEL/CL2 files + + + + + + +